The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2347 lines
106KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. using StringMap = std::unordered_map<String, String>;
  21. static auto toMap (const StringPairArray& array)
  22. {
  23. StringMap result;
  24. for (auto i = 0; i < array.size(); ++i)
  25. result[array.getAllKeys()[i]] = array.getAllValues()[i];
  26. return result;
  27. }
  28. static auto getValueWithDefault (const StringMap& m, const String& key, const String& fallback = {})
  29. {
  30. const auto iter = m.find (key);
  31. return iter != m.cend() ? iter->second : fallback;
  32. }
  33. static const char* const wavFormatName = "WAV file";
  34. //==============================================================================
  35. const char* const WavAudioFormat::bwavDescription = "bwav description";
  36. const char* const WavAudioFormat::bwavOriginator = "bwav originator";
  37. const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref";
  38. const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date";
  39. const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time";
  40. const char* const WavAudioFormat::bwavTimeReference = "bwav time reference";
  41. const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history";
  42. StringPairArray WavAudioFormat::createBWAVMetadata (const String& description,
  43. const String& originator,
  44. const String& originatorRef,
  45. Time date,
  46. int64 timeReferenceSamples,
  47. const String& codingHistory)
  48. {
  49. StringPairArray m;
  50. m.set (bwavDescription, description);
  51. m.set (bwavOriginator, originator);
  52. m.set (bwavOriginatorRef, originatorRef);
  53. m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d"));
  54. m.set (bwavOriginationTime, date.formatted ("%H:%M:%S"));
  55. m.set (bwavTimeReference, String (timeReferenceSamples));
  56. m.set (bwavCodingHistory, codingHistory);
  57. return m;
  58. }
  59. const char* const WavAudioFormat::acidOneShot = "acid one shot";
  60. const char* const WavAudioFormat::acidRootSet = "acid root set";
  61. const char* const WavAudioFormat::acidStretch = "acid stretch";
  62. const char* const WavAudioFormat::acidDiskBased = "acid disk based";
  63. const char* const WavAudioFormat::acidizerFlag = "acidizer flag";
  64. const char* const WavAudioFormat::acidRootNote = "acid root note";
  65. const char* const WavAudioFormat::acidBeats = "acid beats";
  66. const char* const WavAudioFormat::acidDenominator = "acid denominator";
  67. const char* const WavAudioFormat::acidNumerator = "acid numerator";
  68. const char* const WavAudioFormat::acidTempo = "acid tempo";
  69. const char* const WavAudioFormat::riffInfoArchivalLocation = "IARL";
  70. const char* const WavAudioFormat::riffInfoArtist = "IART";
  71. const char* const WavAudioFormat::riffInfoBaseURL = "IBSU";
  72. const char* const WavAudioFormat::riffInfoCinematographer = "ICNM";
  73. const char* const WavAudioFormat::riffInfoComment = "CMNT";
  74. const char* const WavAudioFormat::riffInfoComment2 = "ICMT";
  75. const char* const WavAudioFormat::riffInfoComments = "COMM";
  76. const char* const WavAudioFormat::riffInfoCommissioned = "ICMS";
  77. const char* const WavAudioFormat::riffInfoCopyright = "ICOP";
  78. const char* const WavAudioFormat::riffInfoCostumeDesigner = "ICDS";
  79. const char* const WavAudioFormat::riffInfoCountry = "ICNT";
  80. const char* const WavAudioFormat::riffInfoCropped = "ICRP";
  81. const char* const WavAudioFormat::riffInfoDateCreated = "ICRD";
  82. const char* const WavAudioFormat::riffInfoDateTimeOriginal = "IDIT";
  83. const char* const WavAudioFormat::riffInfoDefaultAudioStream = "ICAS";
  84. const char* const WavAudioFormat::riffInfoDimension = "IDIM";
  85. const char* const WavAudioFormat::riffInfoDirectory = "DIRC";
  86. const char* const WavAudioFormat::riffInfoDistributedBy = "IDST";
  87. const char* const WavAudioFormat::riffInfoDotsPerInch = "IDPI";
  88. const char* const WavAudioFormat::riffInfoEditedBy = "IEDT";
  89. const char* const WavAudioFormat::riffInfoEighthLanguage = "IAS8";
  90. const char* const WavAudioFormat::riffInfoEncodedBy = "CODE";
  91. const char* const WavAudioFormat::riffInfoEndTimecode = "TCDO";
  92. const char* const WavAudioFormat::riffInfoEngineer = "IENG";
  93. const char* const WavAudioFormat::riffInfoFifthLanguage = "IAS5";
  94. const char* const WavAudioFormat::riffInfoFirstLanguage = "IAS1";
  95. const char* const WavAudioFormat::riffInfoFourthLanguage = "IAS4";
  96. const char* const WavAudioFormat::riffInfoGenre = "GENR";
  97. const char* const WavAudioFormat::riffInfoKeywords = "IKEY";
  98. const char* const WavAudioFormat::riffInfoLanguage = "LANG";
  99. const char* const WavAudioFormat::riffInfoLength = "TLEN";
  100. const char* const WavAudioFormat::riffInfoLightness = "ILGT";
  101. const char* const WavAudioFormat::riffInfoLocation = "LOCA";
  102. const char* const WavAudioFormat::riffInfoLogoIconURL = "ILIU";
  103. const char* const WavAudioFormat::riffInfoLogoURL = "ILGU";
  104. const char* const WavAudioFormat::riffInfoMedium = "IMED";
  105. const char* const WavAudioFormat::riffInfoMoreInfoBannerImage = "IMBI";
  106. const char* const WavAudioFormat::riffInfoMoreInfoBannerURL = "IMBU";
  107. const char* const WavAudioFormat::riffInfoMoreInfoText = "IMIT";
  108. const char* const WavAudioFormat::riffInfoMoreInfoURL = "IMIU";
  109. const char* const WavAudioFormat::riffInfoMusicBy = "IMUS";
  110. const char* const WavAudioFormat::riffInfoNinthLanguage = "IAS9";
  111. const char* const WavAudioFormat::riffInfoNumberOfParts = "PRT2";
  112. const char* const WavAudioFormat::riffInfoOrganisation = "TORG";
  113. const char* const WavAudioFormat::riffInfoPart = "PRT1";
  114. const char* const WavAudioFormat::riffInfoProducedBy = "IPRO";
  115. const char* const WavAudioFormat::riffInfoProductName = "IPRD";
  116. const char* const WavAudioFormat::riffInfoProductionDesigner = "IPDS";
  117. const char* const WavAudioFormat::riffInfoProductionStudio = "ISDT";
  118. const char* const WavAudioFormat::riffInfoRate = "RATE";
  119. const char* const WavAudioFormat::riffInfoRated = "AGES";
  120. const char* const WavAudioFormat::riffInfoRating = "IRTD";
  121. const char* const WavAudioFormat::riffInfoRippedBy = "IRIP";
  122. const char* const WavAudioFormat::riffInfoSecondaryGenre = "ISGN";
  123. const char* const WavAudioFormat::riffInfoSecondLanguage = "IAS2";
  124. const char* const WavAudioFormat::riffInfoSeventhLanguage = "IAS7";
  125. const char* const WavAudioFormat::riffInfoSharpness = "ISHP";
  126. const char* const WavAudioFormat::riffInfoSixthLanguage = "IAS6";
  127. const char* const WavAudioFormat::riffInfoSoftware = "ISFT";
  128. const char* const WavAudioFormat::riffInfoSoundSchemeTitle = "DISP";
  129. const char* const WavAudioFormat::riffInfoSource = "ISRC";
  130. const char* const WavAudioFormat::riffInfoSourceFrom = "ISRF";
  131. const char* const WavAudioFormat::riffInfoStarring_ISTR = "ISTR";
  132. const char* const WavAudioFormat::riffInfoStarring_STAR = "STAR";
  133. const char* const WavAudioFormat::riffInfoStartTimecode = "TCOD";
  134. const char* const WavAudioFormat::riffInfoStatistics = "STAT";
  135. const char* const WavAudioFormat::riffInfoSubject = "ISBJ";
  136. const char* const WavAudioFormat::riffInfoTapeName = "TAPE";
  137. const char* const WavAudioFormat::riffInfoTechnician = "ITCH";
  138. const char* const WavAudioFormat::riffInfoThirdLanguage = "IAS3";
  139. const char* const WavAudioFormat::riffInfoTimeCode = "ISMP";
  140. const char* const WavAudioFormat::riffInfoTitle = "INAM";
  141. const char* const WavAudioFormat::riffInfoTrackNo = "IPRT";
  142. const char* const WavAudioFormat::riffInfoTrackNumber = "TRCK";
  143. const char* const WavAudioFormat::riffInfoURL = "TURL";
  144. const char* const WavAudioFormat::riffInfoVegasVersionMajor = "VMAJ";
  145. const char* const WavAudioFormat::riffInfoVegasVersionMinor = "VMIN";
  146. const char* const WavAudioFormat::riffInfoVersion = "TVER";
  147. const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU";
  148. const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
  149. const char* const WavAudioFormat::riffInfoYear = "YEAR";
  150. const char* const WavAudioFormat::aswgContentType = "contentType";
  151. const char* const WavAudioFormat::aswgProject = "project";
  152. const char* const WavAudioFormat::aswgOriginator = "originator";
  153. const char* const WavAudioFormat::aswgOriginatorStudio = "originatorStudio";
  154. const char* const WavAudioFormat::aswgNotes = "notes";
  155. const char* const WavAudioFormat::aswgSession = "session";
  156. const char* const WavAudioFormat::aswgState = "state";
  157. const char* const WavAudioFormat::aswgEditor = "editor";
  158. const char* const WavAudioFormat::aswgMixer = "mixer";
  159. const char* const WavAudioFormat::aswgFxChainName = "fxChainName";
  160. const char* const WavAudioFormat::aswgChannelConfig = "channelConfig";
  161. const char* const WavAudioFormat::aswgAmbisonicFormat = "ambisonicFormat";
  162. const char* const WavAudioFormat::aswgAmbisonicChnOrder = "ambisonicChnOrder";
  163. const char* const WavAudioFormat::aswgAmbisonicNorm = "ambisonicNorm";
  164. const char* const WavAudioFormat::aswgMicType = "micType";
  165. const char* const WavAudioFormat::aswgMicConfig = "micConfig";
  166. const char* const WavAudioFormat::aswgMicDistance = "micDistance";
  167. const char* const WavAudioFormat::aswgRecordingLoc = "recordingLoc";
  168. const char* const WavAudioFormat::aswgIsDesigned = "isDesigned";
  169. const char* const WavAudioFormat::aswgRecEngineer = "recEngineer";
  170. const char* const WavAudioFormat::aswgRecStudio = "recStudio";
  171. const char* const WavAudioFormat::aswgImpulseLocation = "impulseLocation";
  172. const char* const WavAudioFormat::aswgCategory = "category";
  173. const char* const WavAudioFormat::aswgSubCategory = "subCategory";
  174. const char* const WavAudioFormat::aswgCatId = "catId";
  175. const char* const WavAudioFormat::aswgUserCategory = "userCategory";
  176. const char* const WavAudioFormat::aswgUserData = "userData";
  177. const char* const WavAudioFormat::aswgVendorCategory = "vendorCategory";
  178. const char* const WavAudioFormat::aswgFxName = "fxName";
  179. const char* const WavAudioFormat::aswgLibrary = "library";
  180. const char* const WavAudioFormat::aswgCreatorId = "creatorId";
  181. const char* const WavAudioFormat::aswgSourceId = "sourceId";
  182. const char* const WavAudioFormat::aswgRmsPower = "rmsPower";
  183. const char* const WavAudioFormat::aswgLoudness = "loudness";
  184. const char* const WavAudioFormat::aswgLoudnessRange = "loudnessRange";
  185. const char* const WavAudioFormat::aswgMaxPeak = "maxPeak";
  186. const char* const WavAudioFormat::aswgSpecDensity = "specDensity";
  187. const char* const WavAudioFormat::aswgZeroCrossRate = "zeroCrossRate";
  188. const char* const WavAudioFormat::aswgPapr = "papr";
  189. const char* const WavAudioFormat::aswgText = "text";
  190. const char* const WavAudioFormat::aswgEfforts = "efforts";
  191. const char* const WavAudioFormat::aswgEffortType = "effortType";
  192. const char* const WavAudioFormat::aswgProjection = "projection";
  193. const char* const WavAudioFormat::aswgLanguage = "language";
  194. const char* const WavAudioFormat::aswgTimingRestriction = "timingRestriction";
  195. const char* const WavAudioFormat::aswgCharacterName = "characterName";
  196. const char* const WavAudioFormat::aswgCharacterGender = "characterGender";
  197. const char* const WavAudioFormat::aswgCharacterAge = "characterAge";
  198. const char* const WavAudioFormat::aswgCharacterRole = "characterRole";
  199. const char* const WavAudioFormat::aswgActorName = "actorName";
  200. const char* const WavAudioFormat::aswgActorGender = "actorGender";
  201. const char* const WavAudioFormat::aswgDirector = "director";
  202. const char* const WavAudioFormat::aswgDirection = "direction";
  203. const char* const WavAudioFormat::aswgFxUsed = "fxUsed";
  204. const char* const WavAudioFormat::aswgUsageRights = "usageRights";
  205. const char* const WavAudioFormat::aswgIsUnion = "isUnion";
  206. const char* const WavAudioFormat::aswgAccent = "accent";
  207. const char* const WavAudioFormat::aswgEmotion = "emotion";
  208. const char* const WavAudioFormat::aswgComposor = "composor";
  209. const char* const WavAudioFormat::aswgArtist = "artist";
  210. const char* const WavAudioFormat::aswgSongTitle = "songTitle";
  211. const char* const WavAudioFormat::aswgGenre = "genre";
  212. const char* const WavAudioFormat::aswgSubGenre = "subGenre";
  213. const char* const WavAudioFormat::aswgProducer = "producer";
  214. const char* const WavAudioFormat::aswgMusicSup = "musicSup";
  215. const char* const WavAudioFormat::aswgInstrument = "instrument";
  216. const char* const WavAudioFormat::aswgMusicPublisher = "musicPublisher";
  217. const char* const WavAudioFormat::aswgRightsOwner = "rightsOwner";
  218. const char* const WavAudioFormat::aswgIsSource = "isSource";
  219. const char* const WavAudioFormat::aswgIsLoop = "isLoop";
  220. const char* const WavAudioFormat::aswgIntensity = "intensity";
  221. const char* const WavAudioFormat::aswgIsFinal = "isFinal";
  222. const char* const WavAudioFormat::aswgOrderRef = "orderRef";
  223. const char* const WavAudioFormat::aswgIsOst = "isOst";
  224. const char* const WavAudioFormat::aswgIsCinematic = "isCinematic";
  225. const char* const WavAudioFormat::aswgIsLicensed = "isLicensed";
  226. const char* const WavAudioFormat::aswgIsDiegetic = "isDiegetic";
  227. const char* const WavAudioFormat::aswgMusicVersion = "musicVersion";
  228. const char* const WavAudioFormat::aswgIsrcId = "isrcId";
  229. const char* const WavAudioFormat::aswgTempo = "tempo";
  230. const char* const WavAudioFormat::aswgTimeSig = "timeSig";
  231. const char* const WavAudioFormat::aswgInKey = "inKey";
  232. const char* const WavAudioFormat::aswgBillingCode = "billingCode";
  233. const char* const WavAudioFormat::aswgVersion = "IXML_VERSION";
  234. const char* const WavAudioFormat::ISRC = "ISRC";
  235. const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code";
  236. const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
  237. //==============================================================================
  238. namespace WavFileHelpers
  239. {
  240. constexpr inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); }
  241. constexpr inline size_t roundUpSize (size_t sz) noexcept { return (sz + 3) & ~3u; }
  242. #if JUCE_MSVC
  243. #pragma pack (push, 1)
  244. #endif
  245. struct BWAVChunk
  246. {
  247. char description[256];
  248. char originator[32];
  249. char originatorRef[32];
  250. char originationDate[10];
  251. char originationTime[8];
  252. uint32 timeRefLow;
  253. uint32 timeRefHigh;
  254. uint16 version;
  255. uint8 umid[64];
  256. uint8 reserved[190];
  257. char codingHistory[1];
  258. void copyTo (StringMap& values, const int totalSize) const
  259. {
  260. values[WavAudioFormat::bwavDescription] = String::fromUTF8 (description, sizeof (description));
  261. values[WavAudioFormat::bwavOriginator] = String::fromUTF8 (originator, sizeof (originator));
  262. values[WavAudioFormat::bwavOriginatorRef] = String::fromUTF8 (originatorRef, sizeof (originatorRef));
  263. values[WavAudioFormat::bwavOriginationDate] = String::fromUTF8 (originationDate, sizeof (originationDate));
  264. values[WavAudioFormat::bwavOriginationTime] = String::fromUTF8 (originationTime, sizeof (originationTime));
  265. auto timeLow = ByteOrder::swapIfBigEndian (timeRefLow);
  266. auto timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh);
  267. auto time = (((int64) timeHigh) << 32) + timeLow;
  268. values[WavAudioFormat::bwavTimeReference] = String (time);
  269. values[WavAudioFormat::bwavCodingHistory] = String::fromUTF8 (codingHistory, totalSize - (int) offsetof (BWAVChunk, codingHistory));
  270. }
  271. static MemoryBlock createFrom (const StringMap& values)
  272. {
  273. MemoryBlock data (roundUpSize (sizeof (BWAVChunk) + getValueWithDefault (values, WavAudioFormat::bwavCodingHistory).getNumBytesAsUTF8()));
  274. data.fillWith (0);
  275. auto* b = (BWAVChunk*) data.getData();
  276. // Allow these calls to overwrite an extra byte at the end, which is fine as long
  277. // as they get called in the right order.
  278. getValueWithDefault (values, WavAudioFormat::bwavDescription) .copyToUTF8 (b->description, 257);
  279. getValueWithDefault (values, WavAudioFormat::bwavOriginator) .copyToUTF8 (b->originator, 33);
  280. getValueWithDefault (values, WavAudioFormat::bwavOriginatorRef) .copyToUTF8 (b->originatorRef, 33);
  281. getValueWithDefault (values, WavAudioFormat::bwavOriginationDate).copyToUTF8 (b->originationDate, 11);
  282. getValueWithDefault (values, WavAudioFormat::bwavOriginationTime).copyToUTF8 (b->originationTime, 9);
  283. auto time = getValueWithDefault (values, WavAudioFormat::bwavTimeReference).getLargeIntValue();
  284. b->timeRefLow = ByteOrder::swapIfBigEndian ((uint32) (time & 0xffffffff));
  285. b->timeRefHigh = ByteOrder::swapIfBigEndian ((uint32) (time >> 32));
  286. getValueWithDefault (values, WavAudioFormat::bwavCodingHistory).copyToUTF8 (b->codingHistory, 0x7fffffff);
  287. if (b->description[0] != 0
  288. || b->originator[0] != 0
  289. || b->originationDate[0] != 0
  290. || b->originationTime[0] != 0
  291. || b->codingHistory[0] != 0
  292. || time != 0)
  293. {
  294. return data;
  295. }
  296. return {};
  297. }
  298. } JUCE_PACKED;
  299. //==============================================================================
  300. inline AudioChannelSet canonicalWavChannelSet (int numChannels)
  301. {
  302. if (numChannels == 1) return AudioChannelSet::mono();
  303. if (numChannels == 2) return AudioChannelSet::stereo();
  304. if (numChannels == 3) return AudioChannelSet::createLCR();
  305. if (numChannels == 4) return AudioChannelSet::quadraphonic();
  306. if (numChannels == 5) return AudioChannelSet::create5point0();
  307. if (numChannels == 6) return AudioChannelSet::create5point1();
  308. if (numChannels == 7) return AudioChannelSet::create7point0SDDS();
  309. if (numChannels == 8) return AudioChannelSet::create7point1SDDS();
  310. return AudioChannelSet::discreteChannels (numChannels);
  311. }
  312. //==============================================================================
  313. struct SMPLChunk
  314. {
  315. struct SampleLoop
  316. {
  317. uint32 identifier;
  318. uint32 type; // these are different in AIFF and WAV
  319. uint32 start;
  320. uint32 end;
  321. uint32 fraction;
  322. uint32 playCount;
  323. } JUCE_PACKED;
  324. uint32 manufacturer;
  325. uint32 product;
  326. uint32 samplePeriod;
  327. uint32 midiUnityNote;
  328. uint32 midiPitchFraction;
  329. uint32 smpteFormat;
  330. uint32 smpteOffset;
  331. uint32 numSampleLoops;
  332. uint32 samplerData;
  333. SampleLoop loops[1];
  334. template <typename NameType>
  335. static void setValue (StringMap& values, NameType name, uint32 val)
  336. {
  337. values[name] = String (ByteOrder::swapIfBigEndian (val));
  338. }
  339. static void setValue (StringMap& values, int prefix, const char* name, uint32 val)
  340. {
  341. setValue (values, "Loop" + String (prefix) + name, val);
  342. }
  343. void copyTo (StringMap& values, const int totalSize) const
  344. {
  345. setValue (values, "Manufacturer", manufacturer);
  346. setValue (values, "Product", product);
  347. setValue (values, "SamplePeriod", samplePeriod);
  348. setValue (values, "MidiUnityNote", midiUnityNote);
  349. setValue (values, "MidiPitchFraction", midiPitchFraction);
  350. setValue (values, "SmpteFormat", smpteFormat);
  351. setValue (values, "SmpteOffset", smpteOffset);
  352. setValue (values, "NumSampleLoops", numSampleLoops);
  353. setValue (values, "SamplerData", samplerData);
  354. for (int i = 0; i < (int) numSampleLoops; ++i)
  355. {
  356. if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize)
  357. break;
  358. setValue (values, i, "Identifier", loops[i].identifier);
  359. setValue (values, i, "Type", loops[i].type);
  360. setValue (values, i, "Start", loops[i].start);
  361. setValue (values, i, "End", loops[i].end);
  362. setValue (values, i, "Fraction", loops[i].fraction);
  363. setValue (values, i, "PlayCount", loops[i].playCount);
  364. }
  365. }
  366. template <typename NameType>
  367. static uint32 getValue (const StringMap& values, NameType name, const char* def)
  368. {
  369. return ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, name, def).getIntValue());
  370. }
  371. static uint32 getValue (const StringMap& values, int prefix, const char* name, const char* def)
  372. {
  373. return getValue (values, "Loop" + String (prefix) + name, def);
  374. }
  375. static MemoryBlock createFrom (const StringMap& values)
  376. {
  377. MemoryBlock data;
  378. auto numLoops = jmin (64, getValueWithDefault (values, "NumSampleLoops", "0").getIntValue());
  379. data.setSize (roundUpSize (sizeof (SMPLChunk) + (size_t) (jmax (0, numLoops - 1)) * sizeof (SampleLoop)), true);
  380. auto s = static_cast<SMPLChunk*> (data.getData());
  381. s->manufacturer = getValue (values, "Manufacturer", "0");
  382. s->product = getValue (values, "Product", "0");
  383. s->samplePeriod = getValue (values, "SamplePeriod", "0");
  384. s->midiUnityNote = getValue (values, "MidiUnityNote", "60");
  385. s->midiPitchFraction = getValue (values, "MidiPitchFraction", "0");
  386. s->smpteFormat = getValue (values, "SmpteFormat", "0");
  387. s->smpteOffset = getValue (values, "SmpteOffset", "0");
  388. s->numSampleLoops = ByteOrder::swapIfBigEndian ((uint32) numLoops);
  389. s->samplerData = getValue (values, "SamplerData", "0");
  390. for (int i = 0; i < numLoops; ++i)
  391. {
  392. auto& loop = s->loops[i];
  393. loop.identifier = getValue (values, i, "Identifier", "0");
  394. loop.type = getValue (values, i, "Type", "0");
  395. loop.start = getValue (values, i, "Start", "0");
  396. loop.end = getValue (values, i, "End", "0");
  397. loop.fraction = getValue (values, i, "Fraction", "0");
  398. loop.playCount = getValue (values, i, "PlayCount", "0");
  399. }
  400. return data;
  401. }
  402. } JUCE_PACKED;
  403. //==============================================================================
  404. struct InstChunk
  405. {
  406. int8 baseNote;
  407. int8 detune;
  408. int8 gain;
  409. int8 lowNote;
  410. int8 highNote;
  411. int8 lowVelocity;
  412. int8 highVelocity;
  413. static void setValue (StringMap& values, const char* name, int val)
  414. {
  415. values[name] = String (val);
  416. }
  417. void copyTo (StringMap& values) const
  418. {
  419. setValue (values, "MidiUnityNote", baseNote);
  420. setValue (values, "Detune", detune);
  421. setValue (values, "Gain", gain);
  422. setValue (values, "LowNote", lowNote);
  423. setValue (values, "HighNote", highNote);
  424. setValue (values, "LowVelocity", lowVelocity);
  425. setValue (values, "HighVelocity", highVelocity);
  426. }
  427. static int8 getValue (const StringMap& values, const char* name, const char* def)
  428. {
  429. return (int8) getValueWithDefault (values, name, def).getIntValue();
  430. }
  431. static MemoryBlock createFrom (const StringMap& values)
  432. {
  433. MemoryBlock data;
  434. if ( values.find ("LowNote") != values.cend()
  435. && values.find ("HighNote") != values.cend())
  436. {
  437. data.setSize (8, true);
  438. auto* inst = static_cast<InstChunk*> (data.getData());
  439. inst->baseNote = getValue (values, "MidiUnityNote", "60");
  440. inst->detune = getValue (values, "Detune", "0");
  441. inst->gain = getValue (values, "Gain", "0");
  442. inst->lowNote = getValue (values, "LowNote", "0");
  443. inst->highNote = getValue (values, "HighNote", "127");
  444. inst->lowVelocity = getValue (values, "LowVelocity", "1");
  445. inst->highVelocity = getValue (values, "HighVelocity", "127");
  446. }
  447. return data;
  448. }
  449. } JUCE_PACKED;
  450. //==============================================================================
  451. struct CueChunk
  452. {
  453. struct Cue
  454. {
  455. uint32 identifier;
  456. uint32 order;
  457. uint32 chunkID;
  458. uint32 chunkStart;
  459. uint32 blockStart;
  460. uint32 offset;
  461. } JUCE_PACKED;
  462. uint32 numCues;
  463. Cue cues[1];
  464. static void setValue (StringMap& values, int prefix, const char* name, uint32 val)
  465. {
  466. values["Cue" + String (prefix) + name] = String (ByteOrder::swapIfBigEndian (val));
  467. }
  468. void copyTo (StringMap& values, const int totalSize) const
  469. {
  470. values["NumCuePoints"] = String (ByteOrder::swapIfBigEndian (numCues));
  471. for (int i = 0; i < (int) numCues; ++i)
  472. {
  473. if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize)
  474. break;
  475. setValue (values, i, "Identifier", cues[i].identifier);
  476. setValue (values, i, "Order", cues[i].order);
  477. setValue (values, i, "ChunkID", cues[i].chunkID);
  478. setValue (values, i, "ChunkStart", cues[i].chunkStart);
  479. setValue (values, i, "BlockStart", cues[i].blockStart);
  480. setValue (values, i, "Offset", cues[i].offset);
  481. }
  482. }
  483. static MemoryBlock createFrom (const StringMap& values)
  484. {
  485. MemoryBlock data;
  486. const int numCues = getValueWithDefault (values, "NumCuePoints", "0").getIntValue();
  487. if (numCues > 0)
  488. {
  489. data.setSize (roundUpSize (sizeof (CueChunk) + (size_t) (numCues - 1) * sizeof (Cue)), true);
  490. auto c = static_cast<CueChunk*> (data.getData());
  491. c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
  492. const String dataChunkID (chunkName ("data"));
  493. int nextOrder = 0;
  494. #if JUCE_DEBUG
  495. Array<uint32> identifiers;
  496. #endif
  497. for (int i = 0; i < numCues; ++i)
  498. {
  499. auto prefix = "Cue" + String (i);
  500. auto identifier = (uint32) getValueWithDefault (values, prefix + "Identifier", "0").getIntValue();
  501. #if JUCE_DEBUG
  502. jassert (! identifiers.contains (identifier));
  503. identifiers.add (identifier);
  504. #endif
  505. auto order = getValueWithDefault (values, prefix + "Order", String (nextOrder)).getIntValue();
  506. nextOrder = jmax (nextOrder, order) + 1;
  507. auto& cue = c->cues[i];
  508. cue.identifier = ByteOrder::swapIfBigEndian ((uint32) identifier);
  509. cue.order = ByteOrder::swapIfBigEndian ((uint32) order);
  510. cue.chunkID = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "ChunkID", dataChunkID).getIntValue());
  511. cue.chunkStart = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "ChunkStart", "0").getIntValue());
  512. cue.blockStart = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "BlockStart", "0").getIntValue());
  513. cue.offset = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "Offset", "0").getIntValue());
  514. }
  515. }
  516. return data;
  517. }
  518. } JUCE_PACKED;
  519. //==============================================================================
  520. namespace ListChunk
  521. {
  522. static int getValue (const StringMap& values, const String& name)
  523. {
  524. return getValueWithDefault (values, name, "0").getIntValue();
  525. }
  526. static int getValue (const StringMap& values, const String& prefix, const char* name)
  527. {
  528. return getValue (values, prefix + name);
  529. }
  530. static void appendLabelOrNoteChunk (const StringMap& values, const String& prefix,
  531. const int chunkType, MemoryOutputStream& out)
  532. {
  533. auto label = getValueWithDefault (values, prefix + "Text", prefix);
  534. auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
  535. auto chunkLength = 4 + labelLength + (labelLength & 1);
  536. out.writeInt (chunkType);
  537. out.writeInt (chunkLength);
  538. out.writeInt (getValue (values, prefix, "Identifier"));
  539. out.write (label.toUTF8(), (size_t) labelLength);
  540. if ((out.getDataSize() & 1) != 0)
  541. out.writeByte (0);
  542. }
  543. static void appendExtraChunk (const StringMap& values, const String& prefix, MemoryOutputStream& out)
  544. {
  545. auto text = getValueWithDefault (values, prefix + "Text", prefix);
  546. auto textLength = (int) text.getNumBytesAsUTF8() + 1; // include null terminator
  547. auto chunkLength = textLength + 20 + (textLength & 1);
  548. out.writeInt (chunkName ("ltxt"));
  549. out.writeInt (chunkLength);
  550. out.writeInt (getValue (values, prefix, "Identifier"));
  551. out.writeInt (getValue (values, prefix, "SampleLength"));
  552. out.writeInt (getValue (values, prefix, "Purpose"));
  553. out.writeShort ((short) getValue (values, prefix, "Country"));
  554. out.writeShort ((short) getValue (values, prefix, "Language"));
  555. out.writeShort ((short) getValue (values, prefix, "Dialect"));
  556. out.writeShort ((short) getValue (values, prefix, "CodePage"));
  557. out.write (text.toUTF8(), (size_t) textLength);
  558. if ((out.getDataSize() & 1) != 0)
  559. out.writeByte (0);
  560. }
  561. static MemoryBlock createFrom (const StringMap& values)
  562. {
  563. auto numCueLabels = getValue (values, "NumCueLabels");
  564. auto numCueNotes = getValue (values, "NumCueNotes");
  565. auto numCueRegions = getValue (values, "NumCueRegions");
  566. MemoryOutputStream out;
  567. if (numCueLabels + numCueNotes + numCueRegions > 0)
  568. {
  569. out.writeInt (chunkName ("adtl"));
  570. for (int i = 0; i < numCueLabels; ++i)
  571. appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out);
  572. for (int i = 0; i < numCueNotes; ++i)
  573. appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out);
  574. for (int i = 0; i < numCueRegions; ++i)
  575. appendExtraChunk (values, "CueRegion" + String (i), out);
  576. }
  577. return out.getMemoryBlock();
  578. }
  579. }
  580. //==============================================================================
  581. /** Reads a RIFF List Info chunk from a stream positioned just after the size byte. */
  582. namespace ListInfoChunk
  583. {
  584. static const char* const types[] =
  585. {
  586. WavAudioFormat::riffInfoArchivalLocation,
  587. WavAudioFormat::riffInfoArtist,
  588. WavAudioFormat::riffInfoBaseURL,
  589. WavAudioFormat::riffInfoCinematographer,
  590. WavAudioFormat::riffInfoComment,
  591. WavAudioFormat::riffInfoComments,
  592. WavAudioFormat::riffInfoComment2,
  593. WavAudioFormat::riffInfoCommissioned,
  594. WavAudioFormat::riffInfoCopyright,
  595. WavAudioFormat::riffInfoCostumeDesigner,
  596. WavAudioFormat::riffInfoCountry,
  597. WavAudioFormat::riffInfoCropped,
  598. WavAudioFormat::riffInfoDateCreated,
  599. WavAudioFormat::riffInfoDateTimeOriginal,
  600. WavAudioFormat::riffInfoDefaultAudioStream,
  601. WavAudioFormat::riffInfoDimension,
  602. WavAudioFormat::riffInfoDirectory,
  603. WavAudioFormat::riffInfoDistributedBy,
  604. WavAudioFormat::riffInfoDotsPerInch,
  605. WavAudioFormat::riffInfoEditedBy,
  606. WavAudioFormat::riffInfoEighthLanguage,
  607. WavAudioFormat::riffInfoEncodedBy,
  608. WavAudioFormat::riffInfoEndTimecode,
  609. WavAudioFormat::riffInfoEngineer,
  610. WavAudioFormat::riffInfoFifthLanguage,
  611. WavAudioFormat::riffInfoFirstLanguage,
  612. WavAudioFormat::riffInfoFourthLanguage,
  613. WavAudioFormat::riffInfoGenre,
  614. WavAudioFormat::riffInfoKeywords,
  615. WavAudioFormat::riffInfoLanguage,
  616. WavAudioFormat::riffInfoLength,
  617. WavAudioFormat::riffInfoLightness,
  618. WavAudioFormat::riffInfoLocation,
  619. WavAudioFormat::riffInfoLogoIconURL,
  620. WavAudioFormat::riffInfoLogoURL,
  621. WavAudioFormat::riffInfoMedium,
  622. WavAudioFormat::riffInfoMoreInfoBannerImage,
  623. WavAudioFormat::riffInfoMoreInfoBannerURL,
  624. WavAudioFormat::riffInfoMoreInfoText,
  625. WavAudioFormat::riffInfoMoreInfoURL,
  626. WavAudioFormat::riffInfoMusicBy,
  627. WavAudioFormat::riffInfoNinthLanguage,
  628. WavAudioFormat::riffInfoNumberOfParts,
  629. WavAudioFormat::riffInfoOrganisation,
  630. WavAudioFormat::riffInfoPart,
  631. WavAudioFormat::riffInfoProducedBy,
  632. WavAudioFormat::riffInfoProductName,
  633. WavAudioFormat::riffInfoProductionDesigner,
  634. WavAudioFormat::riffInfoProductionStudio,
  635. WavAudioFormat::riffInfoRate,
  636. WavAudioFormat::riffInfoRated,
  637. WavAudioFormat::riffInfoRating,
  638. WavAudioFormat::riffInfoRippedBy,
  639. WavAudioFormat::riffInfoSecondaryGenre,
  640. WavAudioFormat::riffInfoSecondLanguage,
  641. WavAudioFormat::riffInfoSeventhLanguage,
  642. WavAudioFormat::riffInfoSharpness,
  643. WavAudioFormat::riffInfoSixthLanguage,
  644. WavAudioFormat::riffInfoSoftware,
  645. WavAudioFormat::riffInfoSoundSchemeTitle,
  646. WavAudioFormat::riffInfoSource,
  647. WavAudioFormat::riffInfoSourceFrom,
  648. WavAudioFormat::riffInfoStarring_ISTR,
  649. WavAudioFormat::riffInfoStarring_STAR,
  650. WavAudioFormat::riffInfoStartTimecode,
  651. WavAudioFormat::riffInfoStatistics,
  652. WavAudioFormat::riffInfoSubject,
  653. WavAudioFormat::riffInfoTapeName,
  654. WavAudioFormat::riffInfoTechnician,
  655. WavAudioFormat::riffInfoThirdLanguage,
  656. WavAudioFormat::riffInfoTimeCode,
  657. WavAudioFormat::riffInfoTitle,
  658. WavAudioFormat::riffInfoTrackNo,
  659. WavAudioFormat::riffInfoTrackNumber,
  660. WavAudioFormat::riffInfoURL,
  661. WavAudioFormat::riffInfoVegasVersionMajor,
  662. WavAudioFormat::riffInfoVegasVersionMinor,
  663. WavAudioFormat::riffInfoVersion,
  664. WavAudioFormat::riffInfoWatermarkURL,
  665. WavAudioFormat::riffInfoWrittenBy,
  666. WavAudioFormat::riffInfoYear
  667. };
  668. static bool isMatchingTypeIgnoringCase (const int value, const char* const name) noexcept
  669. {
  670. for (int i = 0; i < 4; ++i)
  671. if ((juce_wchar) name[i] != CharacterFunctions::toUpperCase ((juce_wchar) ((value >> (i * 8)) & 0xff)))
  672. return false;
  673. return true;
  674. }
  675. static void addToMetadata (StringMap& values, InputStream& input, int64 chunkEnd)
  676. {
  677. while (input.getPosition() < chunkEnd)
  678. {
  679. auto infoType = input.readInt();
  680. auto infoLength = chunkEnd - input.getPosition();
  681. if (infoLength > 0)
  682. {
  683. infoLength = jmin (infoLength, (int64) input.readInt());
  684. if (infoLength <= 0)
  685. return;
  686. for (auto& type : types)
  687. {
  688. if (isMatchingTypeIgnoringCase (infoType, type))
  689. {
  690. MemoryBlock mb;
  691. input.readIntoMemoryBlock (mb, (ssize_t) infoLength);
  692. values[type] = String::createStringFromData ((const char*) mb.getData(),
  693. (int) mb.getSize());
  694. break;
  695. }
  696. }
  697. }
  698. }
  699. }
  700. static bool writeValue (const StringMap& values, MemoryOutputStream& out, const char* paramName)
  701. {
  702. auto value = getValueWithDefault (values, paramName, {});
  703. if (value.isEmpty())
  704. return false;
  705. auto valueLength = (int) value.getNumBytesAsUTF8() + 1;
  706. auto chunkLength = valueLength + (valueLength & 1);
  707. out.writeInt (chunkName (paramName));
  708. out.writeInt (chunkLength);
  709. out.write (value.toUTF8(), (size_t) valueLength);
  710. if ((out.getDataSize() & 1) != 0)
  711. out.writeByte (0);
  712. return true;
  713. }
  714. static MemoryBlock createFrom (const StringMap& values)
  715. {
  716. MemoryOutputStream out;
  717. out.writeInt (chunkName ("INFO"));
  718. bool anyParamsDefined = false;
  719. for (auto& type : types)
  720. if (writeValue (values, out, type))
  721. anyParamsDefined = true;
  722. return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock();
  723. }
  724. }
  725. //==============================================================================
  726. struct AcidChunk
  727. {
  728. /** Reads an acid RIFF chunk from a stream positioned just after the size byte. */
  729. AcidChunk (InputStream& input, size_t length)
  730. {
  731. zerostruct (*this);
  732. input.read (this, (int) jmin (sizeof (*this), length));
  733. }
  734. AcidChunk (const StringMap& values)
  735. {
  736. zerostruct (*this);
  737. flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01)
  738. | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02)
  739. | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04)
  740. | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08)
  741. | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10);
  742. if (getValueWithDefault (values, WavAudioFormat::acidRootSet).getIntValue() != 0)
  743. rootNote = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidRootNote).getIntValue());
  744. numBeats = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, WavAudioFormat::acidBeats).getIntValue());
  745. meterDenominator = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidDenominator).getIntValue());
  746. meterNumerator = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidNumerator).getIntValue());
  747. const auto iter = values.find (WavAudioFormat::acidTempo);
  748. if (iter != values.cend())
  749. tempo = swapFloatByteOrder (iter->second.getFloatValue());
  750. }
  751. static MemoryBlock createFrom (const StringMap& values)
  752. {
  753. return AcidChunk (values).toMemoryBlock();
  754. }
  755. MemoryBlock toMemoryBlock() const
  756. {
  757. return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
  758. ? MemoryBlock (this, sizeof (*this)) : MemoryBlock();
  759. }
  760. void addToMetadata (StringMap& values) const
  761. {
  762. setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01);
  763. setBoolFlag (values, WavAudioFormat::acidRootSet, 0x02);
  764. setBoolFlag (values, WavAudioFormat::acidStretch, 0x04);
  765. setBoolFlag (values, WavAudioFormat::acidDiskBased, 0x08);
  766. setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10);
  767. if (flags & 0x02) // root note set
  768. values[WavAudioFormat::acidRootNote] = String (ByteOrder::swapIfBigEndian (rootNote));
  769. values[WavAudioFormat::acidBeats] = String (ByteOrder::swapIfBigEndian (numBeats));
  770. values[WavAudioFormat::acidDenominator] = String (ByteOrder::swapIfBigEndian (meterDenominator));
  771. values[WavAudioFormat::acidNumerator] = String (ByteOrder::swapIfBigEndian (meterNumerator));
  772. values[WavAudioFormat::acidTempo] = String (swapFloatByteOrder (tempo));
  773. }
  774. void setBoolFlag (StringMap& values, const char* name, uint32 mask) const
  775. {
  776. values[name] = (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0";
  777. }
  778. static uint32 getFlagIfPresent (const StringMap& values, const char* name, uint32 flag)
  779. {
  780. return getValueWithDefault (values, name).getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0;
  781. }
  782. static float swapFloatByteOrder (const float x) noexcept
  783. {
  784. #ifdef JUCE_BIG_ENDIAN
  785. union { uint32 asInt; float asFloat; } n;
  786. n.asFloat = x;
  787. n.asInt = ByteOrder::swap (n.asInt);
  788. return n.asFloat;
  789. #else
  790. return x;
  791. #endif
  792. }
  793. uint32 flags;
  794. uint16 rootNote;
  795. uint16 reserved1;
  796. float reserved2;
  797. uint32 numBeats;
  798. uint16 meterDenominator;
  799. uint16 meterNumerator;
  800. float tempo;
  801. } JUCE_PACKED;
  802. //==============================================================================
  803. struct TracktionChunk
  804. {
  805. static MemoryBlock createFrom (const StringMap& values)
  806. {
  807. MemoryOutputStream out;
  808. auto s = getValueWithDefault (values, WavAudioFormat::tracktionLoopInfo);
  809. if (s.isNotEmpty())
  810. {
  811. out.writeString (s);
  812. if ((out.getDataSize() & 1) != 0)
  813. out.writeByte (0);
  814. }
  815. return out.getMemoryBlock();
  816. }
  817. };
  818. //==============================================================================
  819. namespace IXMLChunk
  820. {
  821. static const std::unordered_set<String> aswgMetadataKeys
  822. {
  823. WavAudioFormat::aswgContentType,
  824. WavAudioFormat::aswgProject,
  825. WavAudioFormat::aswgOriginator,
  826. WavAudioFormat::aswgOriginatorStudio,
  827. WavAudioFormat::aswgNotes,
  828. WavAudioFormat::aswgSession,
  829. WavAudioFormat::aswgState,
  830. WavAudioFormat::aswgEditor,
  831. WavAudioFormat::aswgMixer,
  832. WavAudioFormat::aswgFxChainName,
  833. WavAudioFormat::aswgChannelConfig,
  834. WavAudioFormat::aswgAmbisonicFormat,
  835. WavAudioFormat::aswgAmbisonicChnOrder,
  836. WavAudioFormat::aswgAmbisonicNorm,
  837. WavAudioFormat::aswgMicType,
  838. WavAudioFormat::aswgMicConfig,
  839. WavAudioFormat::aswgMicDistance,
  840. WavAudioFormat::aswgRecordingLoc,
  841. WavAudioFormat::aswgIsDesigned,
  842. WavAudioFormat::aswgRecEngineer,
  843. WavAudioFormat::aswgRecStudio,
  844. WavAudioFormat::aswgImpulseLocation,
  845. WavAudioFormat::aswgCategory,
  846. WavAudioFormat::aswgSubCategory,
  847. WavAudioFormat::aswgCatId,
  848. WavAudioFormat::aswgUserCategory,
  849. WavAudioFormat::aswgUserData,
  850. WavAudioFormat::aswgVendorCategory,
  851. WavAudioFormat::aswgFxName,
  852. WavAudioFormat::aswgLibrary,
  853. WavAudioFormat::aswgCreatorId,
  854. WavAudioFormat::aswgSourceId,
  855. WavAudioFormat::aswgRmsPower,
  856. WavAudioFormat::aswgLoudness,
  857. WavAudioFormat::aswgLoudnessRange,
  858. WavAudioFormat::aswgMaxPeak,
  859. WavAudioFormat::aswgSpecDensity,
  860. WavAudioFormat::aswgZeroCrossRate,
  861. WavAudioFormat::aswgPapr,
  862. WavAudioFormat::aswgText,
  863. WavAudioFormat::aswgEfforts,
  864. WavAudioFormat::aswgEffortType,
  865. WavAudioFormat::aswgProjection,
  866. WavAudioFormat::aswgLanguage,
  867. WavAudioFormat::aswgTimingRestriction,
  868. WavAudioFormat::aswgCharacterName,
  869. WavAudioFormat::aswgCharacterGender,
  870. WavAudioFormat::aswgCharacterAge,
  871. WavAudioFormat::aswgCharacterRole,
  872. WavAudioFormat::aswgActorName,
  873. WavAudioFormat::aswgActorGender,
  874. WavAudioFormat::aswgDirector,
  875. WavAudioFormat::aswgDirection,
  876. WavAudioFormat::aswgFxUsed,
  877. WavAudioFormat::aswgUsageRights,
  878. WavAudioFormat::aswgIsUnion,
  879. WavAudioFormat::aswgAccent,
  880. WavAudioFormat::aswgEmotion,
  881. WavAudioFormat::aswgComposor,
  882. WavAudioFormat::aswgArtist,
  883. WavAudioFormat::aswgSongTitle,
  884. WavAudioFormat::aswgGenre,
  885. WavAudioFormat::aswgSubGenre,
  886. WavAudioFormat::aswgProducer,
  887. WavAudioFormat::aswgMusicSup,
  888. WavAudioFormat::aswgInstrument,
  889. WavAudioFormat::aswgMusicPublisher,
  890. WavAudioFormat::aswgRightsOwner,
  891. WavAudioFormat::aswgIsSource,
  892. WavAudioFormat::aswgIsLoop,
  893. WavAudioFormat::aswgIntensity,
  894. WavAudioFormat::aswgIsFinal,
  895. WavAudioFormat::aswgOrderRef,
  896. WavAudioFormat::aswgIsOst,
  897. WavAudioFormat::aswgIsCinematic,
  898. WavAudioFormat::aswgIsLicensed,
  899. WavAudioFormat::aswgIsDiegetic,
  900. WavAudioFormat::aswgMusicVersion,
  901. WavAudioFormat::aswgIsrcId,
  902. WavAudioFormat::aswgTempo,
  903. WavAudioFormat::aswgTimeSig,
  904. WavAudioFormat::aswgInKey,
  905. WavAudioFormat::aswgBillingCode
  906. };
  907. static void addToMetadata (StringMap& destValues, const String& source)
  908. {
  909. if (auto xml = parseXML (source))
  910. {
  911. if (xml->hasTagName ("BWFXML"))
  912. {
  913. if (const auto* entry = xml->getChildByName (WavAudioFormat::aswgVersion))
  914. destValues[WavAudioFormat::aswgVersion] = entry->getAllSubText();
  915. if (const auto* aswgElement = xml->getChildByName ("ASWG"))
  916. {
  917. for (const auto* entry : aswgElement->getChildIterator())
  918. {
  919. const auto& tag = entry->getTagName();
  920. if (aswgMetadataKeys.find (tag) != aswgMetadataKeys.end())
  921. destValues[tag] = entry->getAllSubText();
  922. }
  923. }
  924. }
  925. }
  926. }
  927. static MemoryBlock createFrom (const StringMap& values)
  928. {
  929. auto createTextElement = [] (const StringRef& key, const StringRef& value)
  930. {
  931. auto* elem = new XmlElement (key);
  932. elem->addTextElement (value);
  933. return elem;
  934. };
  935. std::unique_ptr<XmlElement> aswgElement;
  936. for (const auto& pair : values)
  937. {
  938. if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
  939. {
  940. if (aswgElement == nullptr)
  941. aswgElement = std::make_unique<XmlElement> ("ASWG");
  942. aswgElement->addChildElement (createTextElement (pair.first, pair.second));
  943. }
  944. }
  945. MemoryOutputStream outputStream;
  946. if (aswgElement != nullptr)
  947. {
  948. XmlElement xml ("BWFXML");
  949. auto aswgVersion = getValueWithDefault (values, WavAudioFormat::aswgVersion, "3.01");
  950. xml.addChildElement (createTextElement (WavAudioFormat::aswgVersion, aswgVersion));
  951. xml.addChildElement (aswgElement.release());
  952. xml.writeTo (outputStream);
  953. outputStream.writeRepeatedByte (0, outputStream.getDataSize());
  954. }
  955. return outputStream.getMemoryBlock();
  956. }
  957. }
  958. //==============================================================================
  959. namespace AXMLChunk
  960. {
  961. static void addToMetadata (StringMap& destValues, const String& source)
  962. {
  963. if (auto xml = parseXML (source))
  964. {
  965. if (xml->hasTagName ("ebucore:ebuCoreMain"))
  966. {
  967. if (auto xml2 = xml->getChildByName ("ebucore:coreMetadata"))
  968. {
  969. if (auto xml3 = xml2->getChildByName ("ebucore:identifier"))
  970. {
  971. if (auto xml4 = xml3->getChildByName ("dc:identifier"))
  972. {
  973. auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf ("ISRC:", false, true);
  974. if (ISRCCode.isNotEmpty())
  975. {
  976. // We set ISRC here for backwards compatibility.
  977. // If the INFO 'source' field is set in the info chunk, then the
  978. // value for this key will be overwritten later.
  979. destValues[WavAudioFormat::riffInfoSource] = destValues[WavAudioFormat::internationalStandardRecordingCode] = ISRCCode;
  980. }
  981. }
  982. }
  983. }
  984. }
  985. }
  986. }
  987. static MemoryBlock createFrom (const StringMap& values)
  988. {
  989. // Use the new ISRC key if it is present, but fall back to the
  990. // INFO 'source' value for backwards compatibility.
  991. auto ISRC = getValueWithDefault (values,
  992. WavAudioFormat::internationalStandardRecordingCode,
  993. getValueWithDefault (values, WavAudioFormat::riffInfoSource));
  994. MemoryOutputStream xml;
  995. if (ISRC.isNotEmpty())
  996. {
  997. // If you are trying to set the ISRC, make sure that you are using
  998. // WavAudioFormat::internationalStandardRecordingCode as the metadata key,
  999. // and that the value is 12 characters long. If you are trying to set the
  1000. // 'source' field in the INFO chunk, set the
  1001. // WavAudioFormat::internationalStandardRecordingCode metadata field to the
  1002. // empty string to silence this assertion.
  1003. jassert (ISRC.length() == 12);
  1004. xml << "<ebucore:ebuCoreMain xmlns:dc=\" http://purl.org/dc/elements/1.1/\" "
  1005. "xmlns:ebucore=\"urn:ebu:metadata-schema:ebuCore_2012\">"
  1006. "<ebucore:coreMetadata>"
  1007. "<ebucore:identifier typeLabel=\"GUID\" "
  1008. "typeDefinition=\"Globally Unique Identifier\" "
  1009. "formatLabel=\"ISRC\" "
  1010. "formatDefinition=\"International Standard Recording Code\" "
  1011. "formatLink=\"http://www.ebu.ch/metadata/cs/ebu_IdentifierTypeCodeCS.xml#3.7\">"
  1012. "<dc:identifier>ISRC:" << ISRC << "</dc:identifier>"
  1013. "</ebucore:identifier>"
  1014. "</ebucore:coreMetadata>"
  1015. "</ebucore:ebuCoreMain>";
  1016. xml.writeRepeatedByte (0, xml.getDataSize()); // ensures even size, null termination and room for future growing
  1017. }
  1018. return xml.getMemoryBlock();
  1019. }
  1020. }
  1021. //==============================================================================
  1022. struct ExtensibleWavSubFormat
  1023. {
  1024. uint32 data1;
  1025. uint16 data2;
  1026. uint16 data3;
  1027. uint8 data4[8];
  1028. bool operator== (const ExtensibleWavSubFormat& other) const noexcept { return memcmp (this, &other, sizeof (*this)) == 0; }
  1029. bool operator!= (const ExtensibleWavSubFormat& other) const noexcept { return ! operator== (other); }
  1030. } JUCE_PACKED;
  1031. static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
  1032. static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
  1033. static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } };
  1034. struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise
  1035. {
  1036. uint32 riffSizeLow; // low 4 byte size of RF64 block
  1037. uint32 riffSizeHigh; // high 4 byte size of RF64 block
  1038. uint32 dataSizeLow; // low 4 byte size of data chunk
  1039. uint32 dataSizeHigh; // high 4 byte size of data chunk
  1040. uint32 sampleCountLow; // low 4 byte sample count of fact chunk
  1041. uint32 sampleCountHigh; // high 4 byte sample count of fact chunk
  1042. uint32 tableLength; // number of valid entries in array 'table'
  1043. } JUCE_PACKED;
  1044. #if JUCE_MSVC
  1045. #pragma pack (pop)
  1046. #endif
  1047. }
  1048. //==============================================================================
  1049. class WavAudioFormatReader final : public AudioFormatReader
  1050. {
  1051. public:
  1052. WavAudioFormatReader (InputStream* in) : AudioFormatReader (in, wavFormatName)
  1053. {
  1054. using namespace WavFileHelpers;
  1055. uint64 len = 0, end = 0;
  1056. int cueNoteIndex = 0;
  1057. int cueLabelIndex = 0;
  1058. int cueRegionIndex = 0;
  1059. StringMap dict;
  1060. auto streamStartPos = input->getPosition();
  1061. auto firstChunkType = input->readInt();
  1062. if (firstChunkType == chunkName ("RF64"))
  1063. {
  1064. input->skipNextBytes (4); // size is -1 for RF64
  1065. isRF64 = true;
  1066. }
  1067. else if (firstChunkType == chunkName ("RIFF"))
  1068. {
  1069. len = (uint64) (uint32) input->readInt();
  1070. end = len + (uint64) input->getPosition();
  1071. }
  1072. else
  1073. {
  1074. return;
  1075. }
  1076. auto startOfRIFFChunk = input->getPosition();
  1077. if (input->readInt() == chunkName ("WAVE"))
  1078. {
  1079. if (isRF64 && input->readInt() == chunkName ("ds64"))
  1080. {
  1081. auto length = (uint32) input->readInt();
  1082. if (length < 28)
  1083. return;
  1084. auto chunkEnd = input->getPosition() + length + (length & 1);
  1085. len = (uint64) input->readInt64();
  1086. end = len + (uint64) startOfRIFFChunk;
  1087. dataLength = input->readInt64();
  1088. input->setPosition (chunkEnd);
  1089. }
  1090. while ((uint64) input->getPosition() < end && ! input->isExhausted())
  1091. {
  1092. auto chunkType = input->readInt();
  1093. auto length = (uint32) input->readInt();
  1094. auto chunkEnd = input->getPosition() + length + (length & 1);
  1095. if (chunkType == chunkName ("fmt "))
  1096. {
  1097. // read the format chunk
  1098. auto format = (unsigned short) input->readShort();
  1099. numChannels = (unsigned int) input->readShort();
  1100. sampleRate = input->readInt();
  1101. auto bytesPerSec = input->readInt();
  1102. input->skipNextBytes (2);
  1103. bitsPerSample = (unsigned int) (int) input->readShort();
  1104. if (bitsPerSample > 64 && (int) sampleRate != 0)
  1105. {
  1106. bytesPerFrame = bytesPerSec / (int) sampleRate;
  1107. if (numChannels != 0)
  1108. bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels;
  1109. }
  1110. else
  1111. {
  1112. bytesPerFrame = (int) (numChannels * bitsPerSample / 8);
  1113. }
  1114. if (format == 3)
  1115. {
  1116. usesFloatingPointData = true;
  1117. }
  1118. else if (format == 0xfffe) // WAVE_FORMAT_EXTENSIBLE
  1119. {
  1120. if (length < 40) // too short
  1121. {
  1122. bytesPerFrame = 0;
  1123. }
  1124. else
  1125. {
  1126. input->skipNextBytes (4); // skip over size and bitsPerSample
  1127. auto channelMask = input->readInt();
  1128. dict["ChannelMask"] = String (channelMask);
  1129. channelLayout = getChannelLayoutFromMask (channelMask, numChannels);
  1130. ExtensibleWavSubFormat subFormat;
  1131. subFormat.data1 = (uint32) input->readInt();
  1132. subFormat.data2 = (uint16) input->readShort();
  1133. subFormat.data3 = (uint16) input->readShort();
  1134. input->read (subFormat.data4, sizeof (subFormat.data4));
  1135. if (subFormat == IEEEFloatFormat)
  1136. usesFloatingPointData = true;
  1137. else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
  1138. bytesPerFrame = 0;
  1139. }
  1140. }
  1141. else if (format == 0x674f // WAVE_FORMAT_OGG_VORBIS_MODE_1
  1142. || format == 0x6750 // WAVE_FORMAT_OGG_VORBIS_MODE_2
  1143. || format == 0x6751 // WAVE_FORMAT_OGG_VORBIS_MODE_3
  1144. || format == 0x676f // WAVE_FORMAT_OGG_VORBIS_MODE_1_PLUS
  1145. || format == 0x6770 // WAVE_FORMAT_OGG_VORBIS_MODE_2_PLUS
  1146. || format == 0x6771) // WAVE_FORMAT_OGG_VORBIS_MODE_3_PLUS
  1147. {
  1148. isSubformatOggVorbis = true;
  1149. sampleRate = 0; // to mark the wav reader as failed
  1150. input->setPosition (streamStartPos);
  1151. return;
  1152. }
  1153. else if (format != 1)
  1154. {
  1155. bytesPerFrame = 0;
  1156. }
  1157. }
  1158. else if (chunkType == chunkName ("data"))
  1159. {
  1160. if (isRF64)
  1161. {
  1162. if (dataLength > 0)
  1163. chunkEnd = input->getPosition() + dataLength + (dataLength & 1);
  1164. }
  1165. else
  1166. {
  1167. dataLength = length;
  1168. }
  1169. dataChunkStart = input->getPosition();
  1170. lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
  1171. }
  1172. else if (chunkType == chunkName ("bext"))
  1173. {
  1174. bwavChunkStart = input->getPosition();
  1175. bwavSize = length;
  1176. HeapBlock<BWAVChunk> bwav;
  1177. bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
  1178. input->read (bwav, (int) length);
  1179. bwav->copyTo (dict, (int) length);
  1180. }
  1181. else if (chunkType == chunkName ("smpl"))
  1182. {
  1183. HeapBlock<SMPLChunk> smpl;
  1184. smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
  1185. input->read (smpl, (int) length);
  1186. smpl->copyTo (dict, (int) length);
  1187. }
  1188. else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
  1189. {
  1190. HeapBlock<InstChunk> inst;
  1191. inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
  1192. input->read (inst, (int) length);
  1193. inst->copyTo (dict);
  1194. }
  1195. else if (chunkType == chunkName ("cue "))
  1196. {
  1197. HeapBlock<CueChunk> cue;
  1198. cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1);
  1199. input->read (cue, (int) length);
  1200. cue->copyTo (dict, (int) length);
  1201. }
  1202. else if (chunkType == chunkName ("axml"))
  1203. {
  1204. MemoryBlock axml;
  1205. input->readIntoMemoryBlock (axml, (ssize_t) length);
  1206. AXMLChunk::addToMetadata (dict, axml.toString());
  1207. }
  1208. else if (chunkType == chunkName ("iXML"))
  1209. {
  1210. MemoryBlock ixml;
  1211. input->readIntoMemoryBlock (ixml, (ssize_t) length);
  1212. IXMLChunk::addToMetadata (dict, ixml.toString());
  1213. }
  1214. else if (chunkType == chunkName ("LIST"))
  1215. {
  1216. auto subChunkType = input->readInt();
  1217. if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO"))
  1218. {
  1219. ListInfoChunk::addToMetadata (dict, *input, chunkEnd);
  1220. }
  1221. else if (subChunkType == chunkName ("adtl"))
  1222. {
  1223. while (input->getPosition() < chunkEnd)
  1224. {
  1225. auto adtlChunkType = input->readInt();
  1226. auto adtlLength = (uint32) input->readInt();
  1227. auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
  1228. if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
  1229. {
  1230. String prefix;
  1231. if (adtlChunkType == chunkName ("labl"))
  1232. prefix << "CueLabel" << cueLabelIndex++;
  1233. else if (adtlChunkType == chunkName ("note"))
  1234. prefix << "CueNote" << cueNoteIndex++;
  1235. auto identifier = (uint32) input->readInt();
  1236. auto stringLength = (int) adtlLength - 4;
  1237. MemoryBlock textBlock;
  1238. input->readIntoMemoryBlock (textBlock, stringLength);
  1239. dict[prefix + "Identifier"] = String (identifier);
  1240. dict[prefix + "Text"] = textBlock.toString();
  1241. }
  1242. else if (adtlChunkType == chunkName ("ltxt"))
  1243. {
  1244. auto prefix = "CueRegion" + String (cueRegionIndex++);
  1245. auto identifier = (uint32) input->readInt();
  1246. auto sampleLength = (uint32) input->readInt();
  1247. auto purpose = (uint32) input->readInt();
  1248. auto country = (uint16) input->readShort();
  1249. auto language = (uint16) input->readShort();
  1250. auto dialect = (uint16) input->readShort();
  1251. auto codePage = (uint16) input->readShort();
  1252. auto stringLength = adtlLength - 20;
  1253. MemoryBlock textBlock;
  1254. input->readIntoMemoryBlock (textBlock, (int) stringLength);
  1255. dict[prefix + "Identifier"] = String (identifier);
  1256. dict[prefix + "SampleLength"] = String (sampleLength);
  1257. dict[prefix + "Purpose"] = String (purpose);
  1258. dict[prefix + "Country"] = String (country);
  1259. dict[prefix + "Language"] = String (language);
  1260. dict[prefix + "Dialect"] = String (dialect);
  1261. dict[prefix + "CodePage"] = String (codePage);
  1262. dict[prefix + "Text"] = textBlock.toString();
  1263. }
  1264. input->setPosition (adtlChunkEnd);
  1265. }
  1266. }
  1267. }
  1268. else if (chunkType == chunkName ("acid"))
  1269. {
  1270. AcidChunk (*input, length).addToMetadata (dict);
  1271. }
  1272. else if (chunkType == chunkName ("Trkn"))
  1273. {
  1274. MemoryBlock tracktion;
  1275. input->readIntoMemoryBlock (tracktion, (ssize_t) length);
  1276. dict[WavAudioFormat::tracktionLoopInfo] = tracktion.toString();
  1277. }
  1278. else if (chunkEnd <= input->getPosition())
  1279. {
  1280. break;
  1281. }
  1282. input->setPosition (chunkEnd);
  1283. }
  1284. }
  1285. if (cueLabelIndex > 0) dict["NumCueLabels"] = String (cueLabelIndex);
  1286. if (cueNoteIndex > 0) dict["NumCueNotes"] = String (cueNoteIndex);
  1287. if (cueRegionIndex > 0) dict["NumCueRegions"] = String (cueRegionIndex);
  1288. if (dict.size() > 0) dict["MetaDataSource"] = "WAV";
  1289. metadataValues.addUnorderedMap (dict);
  1290. }
  1291. //==============================================================================
  1292. bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
  1293. int64 startSampleInFile, int numSamples) override
  1294. {
  1295. clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
  1296. startSampleInFile, numSamples, lengthInSamples);
  1297. if (numSamples <= 0)
  1298. return true;
  1299. input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
  1300. while (numSamples > 0)
  1301. {
  1302. const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
  1303. char tempBuffer[tempBufSize];
  1304. auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
  1305. auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
  1306. if (bytesRead < numThisTime * bytesPerFrame)
  1307. {
  1308. jassert (bytesRead >= 0);
  1309. zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
  1310. }
  1311. copySampleData (bitsPerSample, usesFloatingPointData,
  1312. destSamples, startOffsetInDestBuffer, numDestChannels,
  1313. tempBuffer, (int) numChannels, numThisTime);
  1314. startOffsetInDestBuffer += numThisTime;
  1315. numSamples -= numThisTime;
  1316. }
  1317. return true;
  1318. }
  1319. static void copySampleData (unsigned int numBitsPerSample, const bool floatingPointData,
  1320. int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
  1321. const void* sourceData, int numberOfChannels, int numSamples) noexcept
  1322. {
  1323. switch (numBitsPerSample)
  1324. {
  1325. case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  1326. case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  1327. case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  1328. case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
  1329. else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
  1330. break;
  1331. default: jassertfalse; break;
  1332. }
  1333. }
  1334. //==============================================================================
  1335. AudioChannelSet getChannelLayout() override
  1336. {
  1337. if (channelLayout.size() == static_cast<int> (numChannels))
  1338. return channelLayout;
  1339. return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
  1340. }
  1341. static AudioChannelSet getChannelLayoutFromMask (int dwChannelMask, size_t totalNumChannels)
  1342. {
  1343. AudioChannelSet wavFileChannelLayout;
  1344. // AudioChannelSet and wav's dwChannelMask are compatible
  1345. BigInteger channelBits (dwChannelMask);
  1346. for (auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
  1347. wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
  1348. // channel layout and number of channels do not match
  1349. if (wavFileChannelLayout.size() != static_cast<int> (totalNumChannels))
  1350. {
  1351. // for backward compatibility with old wav files, assume 1 or 2
  1352. // channel wav files are mono/stereo respectively
  1353. if (totalNumChannels <= 2 && dwChannelMask == 0)
  1354. wavFileChannelLayout = AudioChannelSet::canonicalChannelSet (static_cast<int> (totalNumChannels));
  1355. else
  1356. {
  1357. auto discreteSpeaker = static_cast<int> (AudioChannelSet::discreteChannel0);
  1358. while (wavFileChannelLayout.size() < static_cast<int> (totalNumChannels))
  1359. wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
  1360. }
  1361. }
  1362. return wavFileChannelLayout;
  1363. }
  1364. int64 bwavChunkStart = 0, bwavSize = 0;
  1365. int64 dataChunkStart = 0, dataLength = 0;
  1366. int bytesPerFrame = 0;
  1367. bool isRF64 = false;
  1368. bool isSubformatOggVorbis = false;
  1369. AudioChannelSet channelLayout;
  1370. private:
  1371. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
  1372. };
  1373. //==============================================================================
  1374. class WavAudioFormatWriter final : public AudioFormatWriter
  1375. {
  1376. public:
  1377. WavAudioFormatWriter (OutputStream* const out, const double rate,
  1378. const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
  1379. const StringPairArray& metadataValues)
  1380. : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
  1381. {
  1382. using namespace WavFileHelpers;
  1383. if (metadataValues.size() > 0)
  1384. {
  1385. // The meta data should have been sanitised for the WAV format.
  1386. // If it was originally sourced from an AIFF file the MetaDataSource
  1387. // key should be removed (or set to "WAV") once this has been done
  1388. jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
  1389. const auto map = toMap (metadataValues);
  1390. bwavChunk = BWAVChunk::createFrom (map);
  1391. ixmlChunk = IXMLChunk::createFrom (map);
  1392. axmlChunk = AXMLChunk::createFrom (map);
  1393. smplChunk = SMPLChunk::createFrom (map);
  1394. instChunk = InstChunk::createFrom (map);
  1395. cueChunk = CueChunk ::createFrom (map);
  1396. listChunk = ListChunk::createFrom (map);
  1397. listInfoChunk = ListInfoChunk::createFrom (map);
  1398. acidChunk = AcidChunk::createFrom (map);
  1399. trckChunk = TracktionChunk::createFrom (map);
  1400. }
  1401. headerPosition = out->getPosition();
  1402. writeHeader();
  1403. }
  1404. ~WavAudioFormatWriter() override
  1405. {
  1406. writeHeader();
  1407. }
  1408. //==============================================================================
  1409. bool write (const int** data, int numSamples) override
  1410. {
  1411. jassert (numSamples >= 0);
  1412. jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
  1413. if (writeFailed)
  1414. return false;
  1415. auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
  1416. tempBlock.ensureSize (bytes, false);
  1417. switch (bitsPerSample)
  1418. {
  1419. case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  1420. case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  1421. case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  1422. case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  1423. default: jassertfalse; break;
  1424. }
  1425. if (! output->write (tempBlock.getData(), bytes))
  1426. {
  1427. // failed to write to disk, so let's try writing the header.
  1428. // If it's just run out of disk space, then if it does manage
  1429. // to write the header, we'll still have a usable file..
  1430. writeHeader();
  1431. writeFailed = true;
  1432. return false;
  1433. }
  1434. bytesWritten += bytes;
  1435. lengthInSamples += (uint64) numSamples;
  1436. return true;
  1437. }
  1438. bool flush() override
  1439. {
  1440. auto lastWritePos = output->getPosition();
  1441. writeHeader();
  1442. if (output->setPosition (lastWritePos))
  1443. return true;
  1444. // if this fails, you've given it an output stream that can't seek! It needs
  1445. // to be able to seek back to write the header
  1446. jassertfalse;
  1447. return false;
  1448. }
  1449. private:
  1450. MemoryBlock tempBlock, bwavChunk, ixmlChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
  1451. uint64 lengthInSamples = 0, bytesWritten = 0;
  1452. int64 headerPosition = 0;
  1453. bool writeFailed = false;
  1454. void writeHeader()
  1455. {
  1456. if ((bytesWritten & 1) != 0) // pad to an even length
  1457. output->writeByte (0);
  1458. using namespace WavFileHelpers;
  1459. if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
  1460. {
  1461. // if this fails, you've given it an output stream that can't seek! It needs to be
  1462. // able to seek back to go back and write the header after the data has been written.
  1463. jassertfalse;
  1464. return;
  1465. }
  1466. const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
  1467. uint64 audioDataSize = bytesPerFrame * lengthInSamples;
  1468. auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
  1469. const bool isRF64 = (bytesWritten >= 0x100000000LL);
  1470. const bool isWaveFmtEx = isRF64 || (channelMask != 0);
  1471. int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
  1472. + 8 + audioDataSize + (audioDataSize & 1)
  1473. + chunkSize (bwavChunk)
  1474. + chunkSize (ixmlChunk)
  1475. + chunkSize (axmlChunk)
  1476. + chunkSize (smplChunk)
  1477. + chunkSize (instChunk)
  1478. + chunkSize (cueChunk)
  1479. + chunkSize (listChunk)
  1480. + chunkSize (listInfoChunk)
  1481. + chunkSize (acidChunk)
  1482. + chunkSize (trckChunk)
  1483. + (8 + 28)); // (ds64 chunk)
  1484. riffChunkSize += (riffChunkSize & 1);
  1485. if (isRF64)
  1486. writeChunkHeader (chunkName ("RF64"), -1);
  1487. else
  1488. writeChunkHeader (chunkName ("RIFF"), (int) riffChunkSize);
  1489. output->writeInt (chunkName ("WAVE"));
  1490. if (! isRF64)
  1491. {
  1492. #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
  1493. /* NB: This junk chunk is added for padding, so that the header is a fixed size
  1494. regardless of whether it's RF64 or not. That way, we can begin recording a file,
  1495. and when it's finished, can go back and write either a RIFF or RF64 header,
  1496. depending on whether more than 2^32 samples were written.
  1497. The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
  1498. you need to create files for crappy WAV players with bugs that stop them skipping chunks
  1499. which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
  1500. because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
  1501. */
  1502. writeChunkHeader (chunkName ("JUNK"), 28 + (isWaveFmtEx? 0 : 24));
  1503. output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24));
  1504. #endif
  1505. }
  1506. else
  1507. {
  1508. #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
  1509. // If you disable padding, then you MUST NOT write more than 2^32 samples to a file.
  1510. jassertfalse;
  1511. #endif
  1512. writeChunkHeader (chunkName ("ds64"), 28); // chunk size for uncompressed data (no table)
  1513. output->writeInt64 (riffChunkSize);
  1514. output->writeInt64 ((int64) audioDataSize);
  1515. output->writeRepeatedByte (0, 12);
  1516. }
  1517. if (isWaveFmtEx)
  1518. {
  1519. writeChunkHeader (chunkName ("fmt "), 40);
  1520. output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE
  1521. }
  1522. else
  1523. {
  1524. writeChunkHeader (chunkName ("fmt "), 16);
  1525. output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
  1526. : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
  1527. }
  1528. output->writeShort ((short) numChannels);
  1529. output->writeInt ((int) sampleRate);
  1530. output->writeInt ((int) ((double) bytesPerFrame * sampleRate)); // nAvgBytesPerSec
  1531. output->writeShort ((short) bytesPerFrame); // nBlockAlign
  1532. output->writeShort ((short) bitsPerSample); // wBitsPerSample
  1533. if (isWaveFmtEx)
  1534. {
  1535. output->writeShort (22); // cbSize (size of the extension)
  1536. output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
  1537. output->writeInt (channelMask);
  1538. const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
  1539. output->writeInt ((int) subFormat.data1);
  1540. output->writeShort ((short) subFormat.data2);
  1541. output->writeShort ((short) subFormat.data3);
  1542. output->write (subFormat.data4, sizeof (subFormat.data4));
  1543. }
  1544. writeChunk (bwavChunk, chunkName ("bext"));
  1545. writeChunk (ixmlChunk, chunkName ("iXML"));
  1546. writeChunk (axmlChunk, chunkName ("axml"));
  1547. writeChunk (smplChunk, chunkName ("smpl"));
  1548. writeChunk (instChunk, chunkName ("inst"), 7);
  1549. writeChunk (cueChunk, chunkName ("cue "));
  1550. writeChunk (listChunk, chunkName ("LIST"));
  1551. writeChunk (listInfoChunk, chunkName ("LIST"));
  1552. writeChunk (acidChunk, chunkName ("acid"));
  1553. writeChunk (trckChunk, chunkName ("Trkn"));
  1554. writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
  1555. usesFloatingPointData = (bitsPerSample == 32);
  1556. }
  1557. static size_t chunkSize (const MemoryBlock& data) noexcept { return data.isEmpty() ? 0 : (8 + data.getSize()); }
  1558. void writeChunkHeader (int chunkType, int size) const
  1559. {
  1560. output->writeInt (chunkType);
  1561. output->writeInt (size);
  1562. }
  1563. void writeChunk (const MemoryBlock& data, int chunkType, int size = 0) const
  1564. {
  1565. if (! data.isEmpty())
  1566. {
  1567. writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
  1568. *output << data;
  1569. }
  1570. }
  1571. static int getChannelMaskFromChannelLayout (const AudioChannelSet& layout)
  1572. {
  1573. if (layout.isDiscreteLayout())
  1574. return 0;
  1575. // Don't add an extended format chunk for mono and stereo. Basically, all wav players
  1576. // interpret a wav file with only one or two channels to be mono or stereo anyway.
  1577. if (layout == AudioChannelSet::mono() || layout == AudioChannelSet::stereo())
  1578. return 0;
  1579. auto channels = layout.getChannelTypes();
  1580. auto wavChannelMask = 0;
  1581. for (auto channel : channels)
  1582. {
  1583. int wavChannelBit = static_cast<int> (channel) - 1;
  1584. jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
  1585. wavChannelMask |= (1 << wavChannelBit);
  1586. }
  1587. return wavChannelMask;
  1588. }
  1589. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
  1590. };
  1591. //==============================================================================
  1592. class MemoryMappedWavReader final : public MemoryMappedAudioFormatReader
  1593. {
  1594. public:
  1595. MemoryMappedWavReader (const File& wavFile, const WavAudioFormatReader& reader)
  1596. : MemoryMappedAudioFormatReader (wavFile, reader, reader.dataChunkStart,
  1597. reader.dataLength, reader.bytesPerFrame)
  1598. {
  1599. }
  1600. bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
  1601. int64 startSampleInFile, int numSamples) override
  1602. {
  1603. clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
  1604. startSampleInFile, numSamples, lengthInSamples);
  1605. if (numSamples <= 0)
  1606. return true;
  1607. if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
  1608. {
  1609. jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
  1610. return false;
  1611. }
  1612. WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
  1613. destSamples, startOffsetInDestBuffer, numDestChannels,
  1614. sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
  1615. return true;
  1616. }
  1617. void getSample (int64 sample, float* result) const noexcept override
  1618. {
  1619. auto num = (int) numChannels;
  1620. if (map == nullptr || ! mappedSection.contains (sample))
  1621. {
  1622. jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
  1623. zeromem (result, (size_t) num * sizeof (float));
  1624. return;
  1625. }
  1626. auto dest = &result;
  1627. auto source = sampleToPointer (sample);
  1628. switch (bitsPerSample)
  1629. {
  1630. case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  1631. case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  1632. case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  1633. case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
  1634. else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
  1635. break;
  1636. default: jassertfalse; break;
  1637. }
  1638. }
  1639. void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
  1640. {
  1641. numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
  1642. if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
  1643. {
  1644. jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
  1645. for (int i = 0; i < numChannelsToRead; ++i)
  1646. results[i] = {};
  1647. return;
  1648. }
  1649. switch (bitsPerSample)
  1650. {
  1651. case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  1652. case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  1653. case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  1654. case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
  1655. else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
  1656. break;
  1657. default: jassertfalse; break;
  1658. }
  1659. }
  1660. using AudioFormatReader::readMaxLevels;
  1661. private:
  1662. template <typename SampleType>
  1663. void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
  1664. {
  1665. for (int i = 0; i < numChannelsToRead; ++i)
  1666. results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
  1667. }
  1668. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
  1669. };
  1670. //==============================================================================
  1671. WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {}
  1672. WavAudioFormat::~WavAudioFormat() {}
  1673. Array<int> WavAudioFormat::getPossibleSampleRates()
  1674. {
  1675. return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
  1676. 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
  1677. }
  1678. Array<int> WavAudioFormat::getPossibleBitDepths()
  1679. {
  1680. return { 8, 16, 24, 32 };
  1681. }
  1682. bool WavAudioFormat::canDoStereo() { return true; }
  1683. bool WavAudioFormat::canDoMono() { return true; }
  1684. bool WavAudioFormat::isChannelLayoutSupported (const AudioChannelSet& channelSet)
  1685. {
  1686. auto channelTypes = channelSet.getChannelTypes();
  1687. // When
  1688. if (channelSet.isDiscreteLayout())
  1689. return true;
  1690. // WAV supports all channel types from left ... topRearRight
  1691. for (auto channel : channelTypes)
  1692. if (channel < AudioChannelSet::left || channel > AudioChannelSet::topRearRight)
  1693. return false;
  1694. return true;
  1695. }
  1696. AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
  1697. {
  1698. std::unique_ptr<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream));
  1699. #if JUCE_USE_OGGVORBIS
  1700. if (r->isSubformatOggVorbis)
  1701. {
  1702. r->input = nullptr;
  1703. return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
  1704. }
  1705. #endif
  1706. if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
  1707. return r.release();
  1708. if (! deleteStreamIfOpeningFails)
  1709. r->input = nullptr;
  1710. return nullptr;
  1711. }
  1712. MemoryMappedAudioFormatReader* WavAudioFormat::createMemoryMappedReader (const File& file)
  1713. {
  1714. return createMemoryMappedReader (file.createInputStream().release());
  1715. }
  1716. MemoryMappedAudioFormatReader* WavAudioFormat::createMemoryMappedReader (FileInputStream* fin)
  1717. {
  1718. if (fin != nullptr)
  1719. {
  1720. WavAudioFormatReader reader (fin);
  1721. if (reader.lengthInSamples > 0)
  1722. return new MemoryMappedWavReader (fin->getFile(), reader);
  1723. }
  1724. return nullptr;
  1725. }
  1726. AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate,
  1727. unsigned int numChannels, int bitsPerSample,
  1728. const StringPairArray& metadataValues, int qualityOptionIndex)
  1729. {
  1730. return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
  1731. bitsPerSample, metadataValues, qualityOptionIndex);
  1732. }
  1733. AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out,
  1734. double sampleRate,
  1735. const AudioChannelSet& channelLayout,
  1736. int bitsPerSample,
  1737. const StringPairArray& metadataValues,
  1738. int /*qualityOptionIndex*/)
  1739. {
  1740. if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
  1741. return new WavAudioFormatWriter (out, sampleRate, channelLayout,
  1742. (unsigned int) bitsPerSample, metadataValues);
  1743. return nullptr;
  1744. }
  1745. namespace WavFileHelpers
  1746. {
  1747. static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
  1748. {
  1749. TemporaryFile tempFile (file);
  1750. WavAudioFormat wav;
  1751. std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream().release(), true));
  1752. if (reader != nullptr)
  1753. {
  1754. std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
  1755. if (outStream != nullptr)
  1756. {
  1757. std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
  1758. reader->numChannels, (int) reader->bitsPerSample,
  1759. metadata, 0));
  1760. if (writer != nullptr)
  1761. {
  1762. outStream.release();
  1763. bool ok = writer->writeFromAudioReader (*reader, 0, -1);
  1764. writer.reset();
  1765. reader.reset();
  1766. return ok && tempFile.overwriteTargetFileWithTemporary();
  1767. }
  1768. }
  1769. }
  1770. return false;
  1771. }
  1772. }
  1773. bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata)
  1774. {
  1775. using namespace WavFileHelpers;
  1776. std::unique_ptr<WavAudioFormatReader> reader (static_cast<WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream().release(), true)));
  1777. if (reader != nullptr)
  1778. {
  1779. auto bwavPos = reader->bwavChunkStart;
  1780. auto bwavSize = reader->bwavSize;
  1781. reader.reset();
  1782. if (bwavSize > 0)
  1783. {
  1784. auto chunk = BWAVChunk::createFrom (toMap (newMetadata));
  1785. if (chunk.getSize() <= (size_t) bwavSize)
  1786. {
  1787. // the new one will fit in the space available, so write it directly..
  1788. auto oldSize = wavFile.getSize();
  1789. {
  1790. FileOutputStream out (wavFile);
  1791. if (out.openedOk())
  1792. {
  1793. out.setPosition (bwavPos);
  1794. out << chunk;
  1795. out.setPosition (oldSize);
  1796. }
  1797. }
  1798. jassert (wavFile.getSize() == oldSize);
  1799. return true;
  1800. }
  1801. }
  1802. }
  1803. return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
  1804. }
  1805. //==============================================================================
  1806. //==============================================================================
  1807. #if JUCE_UNIT_TESTS
  1808. struct WaveAudioFormatTests final : public UnitTest
  1809. {
  1810. WaveAudioFormatTests()
  1811. : UnitTest ("Wave audio format tests", UnitTestCategories::audio)
  1812. {}
  1813. void runTest() override
  1814. {
  1815. beginTest ("Setting up metadata");
  1816. auto metadataValues = toMap (WavAudioFormat::createBWAVMetadata ("description",
  1817. "originator",
  1818. "originatorRef",
  1819. Time::getCurrentTime(),
  1820. numTestAudioBufferSamples,
  1821. "codingHistory"));
  1822. for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
  1823. metadataValues[WavFileHelpers::ListInfoChunk::types[i]] = WavFileHelpers::ListInfoChunk::types[i];
  1824. metadataValues[WavAudioFormat::internationalStandardRecordingCode] = WavAudioFormat::internationalStandardRecordingCode;
  1825. if (metadataValues.size() > 0)
  1826. metadataValues["MetaDataSource"] = "WAV";
  1827. const auto smplMetadata = createDefaultSMPLMetadata();
  1828. metadataValues.insert (smplMetadata.cbegin(), smplMetadata.cend());
  1829. WavAudioFormat format;
  1830. MemoryBlock memoryBlock;
  1831. StringPairArray metadataArray;
  1832. metadataArray.addUnorderedMap (metadataValues);
  1833. {
  1834. beginTest ("Metadata can be written and read");
  1835. const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray));
  1836. expect (newMetadata == metadataArray, "Somehow, the metadata is different!");
  1837. }
  1838. {
  1839. beginTest ("Files containing a riff info source and an empty ISRC associate the source with the riffInfoSource key");
  1840. StringPairArray meta;
  1841. meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" },
  1842. { WavAudioFormat::internationalStandardRecordingCode, "" } });
  1843. const auto mb = writeToBlock (format, meta);
  1844. checkPatternsPresent (mb, { "INFOISRC" });
  1845. checkPatternsNotPresent (mb, { "ISRC:", "<ebucore" });
  1846. const auto a = getMetadataAfterReading (format, mb);
  1847. expect (a[WavAudioFormat::riffInfoSource] == "customsource");
  1848. expect (a[WavAudioFormat::internationalStandardRecordingCode] == "");
  1849. }
  1850. {
  1851. beginTest ("Files containing a riff info source and no ISRC associate the source with both keys "
  1852. "for backwards compatibility");
  1853. StringPairArray meta;
  1854. meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" } });
  1855. const auto mb = writeToBlock (format, meta);
  1856. checkPatternsPresent (mb, { "INFOISRC", "ISRC:customsource", "<ebucore" });
  1857. const auto a = getMetadataAfterReading (format, mb);
  1858. expect (a[WavAudioFormat::riffInfoSource] == "customsource");
  1859. expect (a[WavAudioFormat::internationalStandardRecordingCode] == "customsource");
  1860. }
  1861. {
  1862. beginTest ("Files containing an ISRC associate the value with the internationalStandardRecordingCode key "
  1863. "and the riffInfoSource key for backwards compatibility");
  1864. StringPairArray meta;
  1865. meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "AABBBCCDDDDD" } });
  1866. const auto mb = writeToBlock (format, meta);
  1867. checkPatternsPresent (mb, { "ISRC:AABBBCCDDDDD", "<ebucore" });
  1868. checkPatternsNotPresent (mb, { "INFOISRC" });
  1869. const auto a = getMetadataAfterReading (format, mb);
  1870. expect (a[WavAudioFormat::riffInfoSource] == "AABBBCCDDDDD");
  1871. expect (a[WavAudioFormat::internationalStandardRecordingCode] == "AABBBCCDDDDD");
  1872. }
  1873. {
  1874. beginTest ("Files containing an ISRC and a riff info source associate the values with the appropriate keys");
  1875. StringPairArray meta;
  1876. meta.addMap ({ { WavAudioFormat::riffInfoSource, "source" } });
  1877. meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "UUVVVXXYYYYY" } });
  1878. const auto mb = writeToBlock (format, meta);
  1879. checkPatternsPresent (mb, { "INFOISRC", "ISRC:UUVVVXXYYYYY", "<ebucore" });
  1880. const auto a = getMetadataAfterReading (format, mb);
  1881. expect (a[WavAudioFormat::riffInfoSource] == "source");
  1882. expect (a[WavAudioFormat::internationalStandardRecordingCode] == "UUVVVXXYYYYY");
  1883. }
  1884. {
  1885. beginTest ("Files containing ASWG metadata read and write correctly");
  1886. MemoryBlock block;
  1887. StringPairArray meta;
  1888. for (const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
  1889. meta.set (key, "Test123&<>");
  1890. {
  1891. auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0));
  1892. expect (writer != nullptr);
  1893. }
  1894. expect ([&]
  1895. {
  1896. auto input = std::make_unique<MemoryInputStream> (block, false);
  1897. while (! input->isExhausted())
  1898. {
  1899. char chunkType[4] {};
  1900. auto pos = input->getPosition();
  1901. input->read (chunkType, 4);
  1902. if (memcmp (chunkType, "iXML", 4) == 0)
  1903. {
  1904. auto length = (uint32) input->readInt();
  1905. MemoryBlock xmlBlock;
  1906. input->readIntoMemoryBlock (xmlBlock, (ssize_t) length);
  1907. return parseXML (xmlBlock.toString()) != nullptr;
  1908. }
  1909. input->setPosition (pos + 1);
  1910. }
  1911. return false;
  1912. }());
  1913. {
  1914. auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true));
  1915. expect (reader != nullptr);
  1916. for (const auto& key : meta.getAllKeys())
  1917. {
  1918. const auto oldValue = meta.getValue (key, "!");
  1919. const auto newValue = reader->metadataValues.getValue (key, "");
  1920. expectEquals (oldValue, newValue);
  1921. }
  1922. expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01");
  1923. }
  1924. }
  1925. }
  1926. private:
  1927. MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta)
  1928. {
  1929. MemoryBlock mb;
  1930. {
  1931. // The destructor of the writer will modify the block, so make sure that we've
  1932. // destroyed the writer before returning the block!
  1933. auto writer = rawToUniquePtr (format.createWriterFor (new MemoryOutputStream (mb, false),
  1934. 44100.0,
  1935. numTestAudioBufferChannels,
  1936. 16,
  1937. meta,
  1938. 0));
  1939. expect (writer != nullptr);
  1940. AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
  1941. expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
  1942. }
  1943. return mb;
  1944. }
  1945. StringPairArray getMetadataAfterReading (WavAudioFormat& format, const MemoryBlock& mb)
  1946. {
  1947. auto reader = rawToUniquePtr (format.createReaderFor (new MemoryInputStream (mb, false), true));
  1948. expect (reader != nullptr);
  1949. return reader->metadataValues;
  1950. }
  1951. template <typename Fn>
  1952. void checkPatterns (const MemoryBlock& mb, const std::vector<std::string>& patterns, Fn&& fn)
  1953. {
  1954. for (const auto& pattern : patterns)
  1955. {
  1956. const auto begin = static_cast<const char*> (mb.getData());
  1957. const auto end = begin + mb.getSize();
  1958. expect (fn (std::search (begin, end, pattern.begin(), pattern.end()), end));
  1959. }
  1960. }
  1961. void checkPatternsPresent (const MemoryBlock& mb, const std::vector<std::string>& patterns)
  1962. {
  1963. checkPatterns (mb, patterns, std::not_equal_to<>{});
  1964. }
  1965. void checkPatternsNotPresent (const MemoryBlock& mb, const std::vector<std::string>& patterns)
  1966. {
  1967. checkPatterns (mb, patterns, std::equal_to<>{});
  1968. }
  1969. enum
  1970. {
  1971. numTestAudioBufferChannels = 2,
  1972. numTestAudioBufferSamples = 256
  1973. };
  1974. static StringMap createDefaultSMPLMetadata()
  1975. {
  1976. StringMap m;
  1977. m["Manufacturer"] = "0";
  1978. m["Product"] = "0";
  1979. m["SamplePeriod"] = "0";
  1980. m["MidiUnityNote"] = "60";
  1981. m["MidiPitchFraction"] = "0";
  1982. m["SmpteFormat"] = "0";
  1983. m["SmpteOffset"] = "0";
  1984. m["NumSampleLoops"] = "0";
  1985. m["SamplerData"] = "0";
  1986. return m;
  1987. }
  1988. JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
  1989. };
  1990. static const WaveAudioFormatTests waveAudioFormatTests;
  1991. #endif
  1992. } // namespace juce