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.

1002 lines
42KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. static const char* const aiffFormatName = "AIFF file";
  16. //==============================================================================
  17. const char* const AiffAudioFormat::appleOneShot = "apple one shot";
  18. const char* const AiffAudioFormat::appleRootSet = "apple root set";
  19. const char* const AiffAudioFormat::appleRootNote = "apple root note";
  20. const char* const AiffAudioFormat::appleBeats = "apple beats";
  21. const char* const AiffAudioFormat::appleDenominator = "apple denominator";
  22. const char* const AiffAudioFormat::appleNumerator = "apple numerator";
  23. const char* const AiffAudioFormat::appleTag = "apple tag";
  24. const char* const AiffAudioFormat::appleKey = "apple key";
  25. //==============================================================================
  26. namespace AiffFileHelpers
  27. {
  28. inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); }
  29. #if JUCE_MSVC
  30. #pragma pack (push, 1)
  31. #endif
  32. //==============================================================================
  33. struct InstChunk
  34. {
  35. struct Loop
  36. {
  37. uint16 type; // these are different in AIFF and WAV
  38. uint16 startIdentifier;
  39. uint16 endIdentifier;
  40. } JUCE_PACKED;
  41. int8 baseNote;
  42. int8 detune;
  43. int8 lowNote;
  44. int8 highNote;
  45. int8 lowVelocity;
  46. int8 highVelocity;
  47. int16 gain;
  48. Loop sustainLoop;
  49. Loop releaseLoop;
  50. void copyTo (StringPairArray& values) const
  51. {
  52. values.set ("MidiUnityNote", String (baseNote));
  53. values.set ("Detune", String (detune));
  54. values.set ("LowNote", String (lowNote));
  55. values.set ("HighNote", String (highNote));
  56. values.set ("LowVelocity", String (lowVelocity));
  57. values.set ("HighVelocity", String (highVelocity));
  58. values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain)));
  59. values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more
  60. values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type)));
  61. values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier)));
  62. values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier)));
  63. values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type)));
  64. values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier)));
  65. values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier)));
  66. }
  67. static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def)
  68. {
  69. return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue());
  70. }
  71. static int8 getValue8 (const StringPairArray& values, const char* name, const char* def)
  72. {
  73. return (int8) values.getValue (name, def).getIntValue();
  74. }
  75. static void create (MemoryBlock& block, const StringPairArray& values)
  76. {
  77. if (values.getAllKeys().contains ("MidiUnityNote", true))
  78. {
  79. block.setSize ((sizeof (InstChunk) + 3) & ~(size_t) 3, true);
  80. auto& inst = *static_cast<InstChunk*> (block.getData());
  81. inst.baseNote = getValue8 (values, "MidiUnityNote", "60");
  82. inst.detune = getValue8 (values, "Detune", "0");
  83. inst.lowNote = getValue8 (values, "LowNote", "0");
  84. inst.highNote = getValue8 (values, "HighNote", "127");
  85. inst.lowVelocity = getValue8 (values, "LowVelocity", "1");
  86. inst.highVelocity = getValue8 (values, "HighVelocity", "127");
  87. inst.gain = (int16) getValue16 (values, "Gain", "0");
  88. inst.sustainLoop.type = getValue16 (values, "Loop0Type", "0");
  89. inst.sustainLoop.startIdentifier = getValue16 (values, "Loop0StartIdentifier", "0");
  90. inst.sustainLoop.endIdentifier = getValue16 (values, "Loop0EndIdentifier", "0");
  91. inst.releaseLoop.type = getValue16 (values, "Loop1Type", "0");
  92. inst.releaseLoop.startIdentifier = getValue16 (values, "Loop1StartIdentifier", "0");
  93. inst.releaseLoop.endIdentifier = getValue16 (values, "Loop1EndIdentifier", "0");
  94. }
  95. }
  96. } JUCE_PACKED;
  97. //==============================================================================
  98. struct BASCChunk
  99. {
  100. enum Key
  101. {
  102. minor = 1,
  103. major = 2,
  104. neither = 3,
  105. both = 4
  106. };
  107. BASCChunk (InputStream& input)
  108. {
  109. zerostruct (*this);
  110. flags = (uint32) input.readIntBigEndian();
  111. numBeats = (uint32) input.readIntBigEndian();
  112. rootNote = (uint16) input.readShortBigEndian();
  113. key = (uint16) input.readShortBigEndian();
  114. timeSigNum = (uint16) input.readShortBigEndian();
  115. timeSigDen = (uint16) input.readShortBigEndian();
  116. oneShot = (uint16) input.readShortBigEndian();
  117. input.read (unknown, sizeof (unknown));
  118. }
  119. void addToMetadata (StringPairArray& metadata) const
  120. {
  121. const bool rootNoteSet = rootNote != 0;
  122. setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2);
  123. setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet);
  124. if (rootNoteSet)
  125. metadata.set (AiffAudioFormat::appleRootNote, String (rootNote));
  126. metadata.set (AiffAudioFormat::appleBeats, String (numBeats));
  127. metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen));
  128. metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum));
  129. const char* keyString = nullptr;
  130. switch (key)
  131. {
  132. case minor: keyString = "minor"; break;
  133. case major: keyString = "major"; break;
  134. case neither: keyString = "neither"; break;
  135. case both: keyString = "both"; break;
  136. default: break;
  137. }
  138. if (keyString != nullptr)
  139. metadata.set (AiffAudioFormat::appleKey, keyString);
  140. }
  141. void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const
  142. {
  143. values.set (name, shouldBeSet ? "1" : "0");
  144. }
  145. uint32 flags;
  146. uint32 numBeats;
  147. uint16 rootNote;
  148. uint16 key;
  149. uint16 timeSigNum;
  150. uint16 timeSigDen;
  151. uint16 oneShot;
  152. uint8 unknown[66];
  153. } JUCE_PACKED;
  154. #if JUCE_MSVC
  155. #pragma pack (pop)
  156. #endif
  157. //==============================================================================
  158. namespace CATEChunk
  159. {
  160. static bool isValidTag (const char* d) noexcept
  161. {
  162. return CharacterFunctions::isLetterOrDigit (d[0]) && CharacterFunctions::isUpperCase (static_cast<juce_wchar> (d[0]))
  163. && CharacterFunctions::isLetterOrDigit (d[1]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[1]))
  164. && CharacterFunctions::isLetterOrDigit (d[2]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[2]));
  165. }
  166. static bool isAppleGenre (const String& tag) noexcept
  167. {
  168. static const char* appleGenres[] =
  169. {
  170. "Rock/Blues",
  171. "Electronic/Dance",
  172. "Jazz",
  173. "Urban",
  174. "World/Ethnic",
  175. "Cinematic/New Age",
  176. "Orchestral",
  177. "Country/Folk",
  178. "Experimental",
  179. "Other Genre"
  180. };
  181. for (int i = 0; i < numElementsInArray (appleGenres); ++i)
  182. if (tag == appleGenres[i])
  183. return true;
  184. return false;
  185. }
  186. static String read (InputStream& input, const uint32 length)
  187. {
  188. MemoryBlock mb;
  189. input.skipNextBytes (4);
  190. input.readIntoMemoryBlock (mb, (ssize_t) length - 4);
  191. StringArray tagsArray;
  192. auto* data = static_cast<const char*> (mb.getData());
  193. auto* dataEnd = data + mb.getSize();
  194. while (data < dataEnd)
  195. {
  196. bool isGenre = false;
  197. if (isValidTag (data))
  198. {
  199. auto tag = String (CharPointer_UTF8 (data), CharPointer_UTF8 (dataEnd));
  200. isGenre = isAppleGenre (tag);
  201. tagsArray.add (tag);
  202. }
  203. data += isGenre ? 118 : 50;
  204. if (data < dataEnd && data[0] == 0)
  205. {
  206. if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50;
  207. else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118;
  208. else if (data + 170 < dataEnd && isValidTag (data + 168)) data += 168;
  209. }
  210. }
  211. return tagsArray.joinIntoString (";");
  212. }
  213. }
  214. //==============================================================================
  215. namespace MarkChunk
  216. {
  217. static bool metaDataContainsZeroIdentifiers (const StringPairArray& values)
  218. {
  219. // (zero cue identifiers are valid for WAV but not for AIFF)
  220. const String cueString ("Cue");
  221. const String noteString ("CueNote");
  222. const String identifierString ("Identifier");
  223. for (auto& key : values.getAllKeys())
  224. {
  225. if (key.startsWith (noteString))
  226. continue; // zero identifier IS valid in a COMT chunk
  227. if (key.startsWith (cueString) && key.contains (identifierString))
  228. if (values.getValue (key, "-1").getIntValue() == 0)
  229. return true;
  230. }
  231. return false;
  232. }
  233. static void create (MemoryBlock& block, const StringPairArray& values)
  234. {
  235. auto numCues = values.getValue ("NumCuePoints", "0").getIntValue();
  236. if (numCues > 0)
  237. {
  238. MemoryOutputStream out (block, false);
  239. out.writeShortBigEndian ((short) numCues);
  240. auto numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue();
  241. auto idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF
  242. #if JUCE_DEBUG
  243. Array<int> identifiers;
  244. #endif
  245. for (int i = 0; i < numCues; ++i)
  246. {
  247. auto prefixCue = "Cue" + String (i);
  248. auto identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue();
  249. #if JUCE_DEBUG
  250. jassert (! identifiers.contains (identifier));
  251. identifiers.add (identifier);
  252. #endif
  253. auto offset = values.getValue (prefixCue + "Offset", "0").getIntValue();
  254. auto label = "CueLabel" + String (i);
  255. for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex)
  256. {
  257. auto prefixLabel = "CueLabel" + String (labelIndex);
  258. auto labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue();
  259. if (labelIdentifier == identifier)
  260. {
  261. label = values.getValue (prefixLabel + "Text", label);
  262. break;
  263. }
  264. }
  265. out.writeShortBigEndian ((short) identifier);
  266. out.writeIntBigEndian (offset);
  267. auto labelLength = jmin ((size_t) 254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring
  268. out.writeByte (static_cast<char> (labelLength + 1));
  269. out.write (label.toUTF8(), labelLength);
  270. out.writeByte (0);
  271. if ((out.getDataSize() & 1) != 0)
  272. out.writeByte (0);
  273. }
  274. }
  275. }
  276. }
  277. //==============================================================================
  278. namespace COMTChunk
  279. {
  280. static void create (MemoryBlock& block, const StringPairArray& values)
  281. {
  282. auto numNotes = values.getValue ("NumCueNotes", "0").getIntValue();
  283. if (numNotes > 0)
  284. {
  285. MemoryOutputStream out (block, false);
  286. out.writeShortBigEndian ((short) numNotes);
  287. for (int i = 0; i < numNotes; ++i)
  288. {
  289. auto prefix = "CueNote" + String (i);
  290. out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue());
  291. out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue());
  292. auto comment = values.getValue (prefix + "Text", String());
  293. auto commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534);
  294. out.writeShortBigEndian (static_cast<short> (commentLength + 1));
  295. out.write (comment.toUTF8(), commentLength);
  296. out.writeByte (0);
  297. if ((out.getDataSize() & 1) != 0)
  298. out.writeByte (0);
  299. }
  300. }
  301. }
  302. }
  303. }
  304. //==============================================================================
  305. class AiffAudioFormatReader : public AudioFormatReader
  306. {
  307. public:
  308. AiffAudioFormatReader (InputStream* in)
  309. : AudioFormatReader (in, aiffFormatName)
  310. {
  311. using namespace AiffFileHelpers;
  312. if (input->readInt() == chunkName ("FORM"))
  313. {
  314. auto len = input->readIntBigEndian();
  315. auto end = input->getPosition() + len;
  316. auto nextType = input->readInt();
  317. if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC"))
  318. {
  319. bool hasGotVer = false;
  320. bool hasGotData = false;
  321. bool hasGotType = false;
  322. while (input->getPosition() < end)
  323. {
  324. auto type = input->readInt();
  325. auto length = (uint32) input->readIntBigEndian();
  326. auto chunkEnd = input->getPosition() + length;
  327. if (type == chunkName ("FVER"))
  328. {
  329. hasGotVer = true;
  330. auto ver = input->readIntBigEndian();
  331. if (ver != 0 && ver != (int) 0xa2805140)
  332. break;
  333. }
  334. else if (type == chunkName ("COMM"))
  335. {
  336. hasGotType = true;
  337. numChannels = (unsigned int) input->readShortBigEndian();
  338. lengthInSamples = input->readIntBigEndian();
  339. bitsPerSample = (unsigned int) input->readShortBigEndian();
  340. bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
  341. unsigned char sampleRateBytes[10];
  342. input->read (sampleRateBytes, 10);
  343. const int byte0 = sampleRateBytes[0];
  344. if ((byte0 & 0x80) != 0
  345. || byte0 <= 0x3F || byte0 > 0x40
  346. || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
  347. break;
  348. auto sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2);
  349. sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes));
  350. sampleRate = (int) sampRate;
  351. if (length <= 18)
  352. {
  353. // some types don't have a chunk large enough to include a compression
  354. // type, so assume it's just big-endian pcm
  355. littleEndian = false;
  356. }
  357. else
  358. {
  359. auto compType = input->readInt();
  360. if (compType == chunkName ("NONE") || compType == chunkName ("twos"))
  361. {
  362. littleEndian = false;
  363. }
  364. else if (compType == chunkName ("sowt"))
  365. {
  366. littleEndian = true;
  367. }
  368. else if (compType == chunkName ("fl32") || compType == chunkName ("FL32"))
  369. {
  370. littleEndian = false;
  371. usesFloatingPointData = true;
  372. }
  373. else
  374. {
  375. sampleRate = 0;
  376. break;
  377. }
  378. }
  379. }
  380. else if (type == chunkName ("SSND"))
  381. {
  382. hasGotData = true;
  383. auto offset = input->readIntBigEndian();
  384. dataChunkStart = input->getPosition() + 4 + offset;
  385. lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0;
  386. }
  387. else if (type == chunkName ("MARK"))
  388. {
  389. auto numCues = (uint16) input->readShortBigEndian();
  390. // these two are always the same for AIFF-read files
  391. metadataValues.set ("NumCuePoints", String (numCues));
  392. metadataValues.set ("NumCueLabels", String (numCues));
  393. for (uint16 i = 0; i < numCues; ++i)
  394. {
  395. auto identifier = (uint16) input->readShortBigEndian();
  396. auto offset = (uint32) input->readIntBigEndian();
  397. auto stringLength = (uint8) input->readByte();
  398. MemoryBlock textBlock;
  399. input->readIntoMemoryBlock (textBlock, stringLength);
  400. // if the stringLength is even then read one more byte as the
  401. // string needs to be an even number of bytes INCLUDING the
  402. // leading length character in the pascal string
  403. if ((stringLength & 1) == 0)
  404. input->readByte();
  405. auto prefixCue = "Cue" + String (i);
  406. metadataValues.set (prefixCue + "Identifier", String (identifier));
  407. metadataValues.set (prefixCue + "Offset", String (offset));
  408. auto prefixLabel = "CueLabel" + String (i);
  409. metadataValues.set (prefixLabel + "Identifier", String (identifier));
  410. metadataValues.set (prefixLabel + "Text", textBlock.toString());
  411. }
  412. }
  413. else if (type == chunkName ("COMT"))
  414. {
  415. auto numNotes = (uint16) input->readShortBigEndian();
  416. metadataValues.set ("NumCueNotes", String (numNotes));
  417. for (uint16 i = 0; i < numNotes; ++i)
  418. {
  419. auto timestamp = (uint32) input->readIntBigEndian();
  420. auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
  421. auto stringLength = (uint16) input->readShortBigEndian();
  422. MemoryBlock textBlock;
  423. input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
  424. auto prefix = "CueNote" + String (i);
  425. metadataValues.set (prefix + "TimeStamp", String (timestamp));
  426. metadataValues.set (prefix + "Identifier", String (identifier));
  427. metadataValues.set (prefix + "Text", textBlock.toString());
  428. }
  429. }
  430. else if (type == chunkName ("INST"))
  431. {
  432. HeapBlock<InstChunk> inst;
  433. inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
  434. input->read (inst, (int) length);
  435. inst->copyTo (metadataValues);
  436. }
  437. else if (type == chunkName ("basc"))
  438. {
  439. AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues);
  440. }
  441. else if (type == chunkName ("cate"))
  442. {
  443. metadataValues.set (AiffAudioFormat::appleTag,
  444. AiffFileHelpers::CATEChunk::read (*input, length));
  445. }
  446. else if ((hasGotVer && hasGotData && hasGotType)
  447. || chunkEnd < input->getPosition()
  448. || input->isExhausted())
  449. {
  450. break;
  451. }
  452. input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address)
  453. }
  454. }
  455. }
  456. if (metadataValues.size() > 0)
  457. metadataValues.set ("MetaDataSource", "AIFF");
  458. }
  459. //==============================================================================
  460. bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
  461. int64 startSampleInFile, int numSamples) override
  462. {
  463. clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
  464. startSampleInFile, numSamples, lengthInSamples);
  465. if (numSamples <= 0)
  466. return true;
  467. input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
  468. while (numSamples > 0)
  469. {
  470. const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
  471. char tempBuffer [tempBufSize];
  472. const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
  473. const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
  474. if (bytesRead < numThisTime * bytesPerFrame)
  475. {
  476. jassert (bytesRead >= 0);
  477. zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
  478. }
  479. if (littleEndian)
  480. copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData,
  481. destSamples, startOffsetInDestBuffer, numDestChannels,
  482. tempBuffer, (int) numChannels, numThisTime);
  483. else
  484. copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData,
  485. destSamples, startOffsetInDestBuffer, numDestChannels,
  486. tempBuffer, (int) numChannels, numThisTime);
  487. startOffsetInDestBuffer += numThisTime;
  488. numSamples -= numThisTime;
  489. }
  490. return true;
  491. }
  492. template <typename Endianness>
  493. static void copySampleData (unsigned int numBitsPerSample, bool floatingPointData,
  494. int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
  495. const void* sourceData, int numberOfChannels, int numSamples) noexcept
  496. {
  497. switch (numBitsPerSample)
  498. {
  499. case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  500. case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  501. case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
  502. case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
  503. else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
  504. break;
  505. default: jassertfalse; break;
  506. }
  507. }
  508. int bytesPerFrame;
  509. int64 dataChunkStart;
  510. bool littleEndian;
  511. private:
  512. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
  513. };
  514. //==============================================================================
  515. class AiffAudioFormatWriter : public AudioFormatWriter
  516. {
  517. public:
  518. AiffAudioFormatWriter (OutputStream* out, double rate,
  519. unsigned int numChans, unsigned int bits,
  520. const StringPairArray& metadataValues)
  521. : AudioFormatWriter (out, aiffFormatName, rate, numChans, bits)
  522. {
  523. using namespace AiffFileHelpers;
  524. if (metadataValues.size() > 0)
  525. {
  526. // The meta data should have been sanitised for the AIFF format.
  527. // If it was originally sourced from a WAV file the MetaDataSource
  528. // key should be removed (or set to "AIFF") once this has been done
  529. jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV");
  530. MarkChunk::create (markChunk, metadataValues);
  531. COMTChunk::create (comtChunk, metadataValues);
  532. InstChunk::create (instChunk, metadataValues);
  533. }
  534. headerPosition = out->getPosition();
  535. writeHeader();
  536. }
  537. ~AiffAudioFormatWriter() override
  538. {
  539. if ((bytesWritten & 1) != 0)
  540. output->writeByte (0);
  541. writeHeader();
  542. }
  543. //==============================================================================
  544. bool write (const int** data, int numSamples) override
  545. {
  546. jassert (numSamples >= 0);
  547. jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
  548. if (writeFailed)
  549. return false;
  550. auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
  551. tempBlock.ensureSize (bytes, false);
  552. switch (bitsPerSample)
  553. {
  554. case 8: WriteHelper<AudioData::Int8, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  555. case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  556. case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  557. case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
  558. default: jassertfalse; break;
  559. }
  560. if (bytesWritten + bytes >= (size_t) 0xfff00000
  561. || ! output->write (tempBlock.getData(), bytes))
  562. {
  563. // failed to write to disk, so let's try writing the header.
  564. // If it's just run out of disk space, then if it does manage
  565. // to write the header, we'll still have a useable file..
  566. writeHeader();
  567. writeFailed = true;
  568. return false;
  569. }
  570. bytesWritten += bytes;
  571. lengthInSamples += (uint64) numSamples;
  572. return true;
  573. }
  574. private:
  575. MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
  576. uint64 lengthInSamples = 0, bytesWritten = 0;
  577. int64 headerPosition = 0;
  578. bool writeFailed = false;
  579. void writeHeader()
  580. {
  581. using namespace AiffFileHelpers;
  582. const bool couldSeekOk = output->setPosition (headerPosition);
  583. ignoreUnused (couldSeekOk);
  584. // if this fails, you've given it an output stream that can't seek! It needs
  585. // to be able to seek back to write the header
  586. jassert (couldSeekOk);
  587. auto headerLen = (int) (54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0)
  588. + (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0)
  589. + (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0));
  590. auto audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8));
  591. audioBytes += (audioBytes & 1);
  592. output->writeInt (chunkName ("FORM"));
  593. output->writeIntBigEndian (headerLen + audioBytes - 8);
  594. output->writeInt (chunkName ("AIFF"));
  595. output->writeInt (chunkName ("COMM"));
  596. output->writeIntBigEndian (18);
  597. output->writeShortBigEndian ((short) numChannels);
  598. output->writeIntBigEndian ((int) lengthInSamples);
  599. output->writeShortBigEndian ((short) bitsPerSample);
  600. uint8 sampleRateBytes[10] = {};
  601. if (sampleRate <= 1)
  602. {
  603. sampleRateBytes[0] = 0x3f;
  604. sampleRateBytes[1] = 0xff;
  605. sampleRateBytes[2] = 0x80;
  606. }
  607. else
  608. {
  609. int mask = 0x40000000;
  610. sampleRateBytes[0] = 0x40;
  611. if (sampleRate >= mask)
  612. {
  613. jassertfalse;
  614. sampleRateBytes[1] = 0x1d;
  615. }
  616. else
  617. {
  618. int n = (int) sampleRate;
  619. int i;
  620. for (i = 0; i <= 32 ; ++i)
  621. {
  622. if ((n & mask) != 0)
  623. break;
  624. mask >>= 1;
  625. }
  626. n = n << (i + 1);
  627. sampleRateBytes[1] = (uint8) (29 - i);
  628. sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff);
  629. sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff);
  630. sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff);
  631. sampleRateBytes[5] = (uint8) (n & 0xff);
  632. }
  633. }
  634. output->write (sampleRateBytes, 10);
  635. if (markChunk.getSize() > 0)
  636. {
  637. output->writeInt (chunkName ("MARK"));
  638. output->writeIntBigEndian ((int) markChunk.getSize());
  639. *output << markChunk;
  640. }
  641. if (comtChunk.getSize() > 0)
  642. {
  643. output->writeInt (chunkName ("COMT"));
  644. output->writeIntBigEndian ((int) comtChunk.getSize());
  645. *output << comtChunk;
  646. }
  647. if (instChunk.getSize() > 0)
  648. {
  649. output->writeInt (chunkName ("INST"));
  650. output->writeIntBigEndian ((int) instChunk.getSize());
  651. *output << instChunk;
  652. }
  653. output->writeInt (chunkName ("SSND"));
  654. output->writeIntBigEndian (audioBytes + 8);
  655. output->writeInt (0);
  656. output->writeInt (0);
  657. jassert (output->getPosition() == headerLen);
  658. }
  659. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter)
  660. };
  661. //==============================================================================
  662. class MemoryMappedAiffReader : public MemoryMappedAudioFormatReader
  663. {
  664. public:
  665. MemoryMappedAiffReader (const File& f, const AiffAudioFormatReader& reader)
  666. : MemoryMappedAudioFormatReader (f, reader, reader.dataChunkStart,
  667. reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame),
  668. littleEndian (reader.littleEndian)
  669. {
  670. }
  671. bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
  672. int64 startSampleInFile, int numSamples) override
  673. {
  674. clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
  675. startSampleInFile, numSamples, lengthInSamples);
  676. if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
  677. {
  678. jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
  679. return false;
  680. }
  681. if (littleEndian)
  682. AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
  683. (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
  684. numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
  685. else
  686. AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
  687. (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
  688. numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
  689. return true;
  690. }
  691. void getSample (int64 sample, float* result) const noexcept override
  692. {
  693. auto num = (int) numChannels;
  694. if (map == nullptr || ! mappedSection.contains (sample))
  695. {
  696. jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
  697. zeromem (result, (size_t) num * sizeof (float));
  698. return;
  699. }
  700. float** dest = &result;
  701. const void* source = sampleToPointer (sample);
  702. if (littleEndian)
  703. {
  704. switch (bitsPerSample)
  705. {
  706. case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  707. case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  708. case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
  709. case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
  710. else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
  711. break;
  712. default: jassertfalse; break;
  713. }
  714. }
  715. else
  716. {
  717. switch (bitsPerSample)
  718. {
  719. case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
  720. case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
  721. case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
  722. case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
  723. else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
  724. break;
  725. default: jassertfalse; break;
  726. }
  727. }
  728. }
  729. void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
  730. {
  731. numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
  732. if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
  733. {
  734. jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
  735. for (int i = 0; i < numChannelsToRead; ++i)
  736. results[i] = Range<float>();
  737. return;
  738. }
  739. switch (bitsPerSample)
  740. {
  741. case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  742. case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  743. case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
  744. case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
  745. else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
  746. break;
  747. default: jassertfalse; break;
  748. }
  749. }
  750. using AudioFormatReader::readMaxLevels;
  751. private:
  752. const bool littleEndian;
  753. template <typename SampleType>
  754. void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
  755. {
  756. for (int i = 0; i < numChannelsToRead; ++i)
  757. results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
  758. }
  759. template <typename SampleType>
  760. Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
  761. {
  762. return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
  763. : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
  764. }
  765. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
  766. };
  767. //==============================================================================
  768. AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {}
  769. AiffAudioFormat::~AiffAudioFormat() {}
  770. Array<int> AiffAudioFormat::getPossibleSampleRates()
  771. {
  772. return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
  773. }
  774. Array<int> AiffAudioFormat::getPossibleBitDepths()
  775. {
  776. return { 8, 16, 24 };
  777. }
  778. bool AiffAudioFormat::canDoStereo() { return true; }
  779. bool AiffAudioFormat::canDoMono() { return true; }
  780. #if JUCE_MAC
  781. bool AiffAudioFormat::canHandleFile (const File& f)
  782. {
  783. if (AudioFormat::canHandleFile (f))
  784. return true;
  785. auto type = f.getMacOSType();
  786. // (NB: written as hex to avoid four-char-constant warnings)
  787. return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
  788. || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
  789. }
  790. #endif
  791. AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
  792. {
  793. std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
  794. if (w->sampleRate > 0 && w->numChannels > 0)
  795. return w.release();
  796. if (! deleteStreamIfOpeningFails)
  797. w->input = nullptr;
  798. return nullptr;
  799. }
  800. MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (const File& file)
  801. {
  802. return createMemoryMappedReader (file.createInputStream().release());
  803. }
  804. MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (FileInputStream* fin)
  805. {
  806. if (fin != nullptr)
  807. {
  808. AiffAudioFormatReader reader (fin);
  809. if (reader.lengthInSamples > 0)
  810. return new MemoryMappedAiffReader (fin->getFile(), reader);
  811. }
  812. return nullptr;
  813. }
  814. AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out,
  815. double sampleRate,
  816. unsigned int numberOfChannels,
  817. int bitsPerSample,
  818. const StringPairArray& metadataValues,
  819. int /*qualityOptionIndex*/)
  820. {
  821. if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
  822. return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
  823. (unsigned int) bitsPerSample, metadataValues);
  824. return nullptr;
  825. }
  826. } // namespace juce