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.

1007 lines
42KB

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