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.

908 lines
38KB

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