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.

1017 lines
43KB

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