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.

1011 lines
43KB

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