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.

1018 lines
43KB

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