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.

268 lines
7.8KB

  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. namespace CDReaderHelpers
  22. {
  23. inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key)
  24. {
  25. forEachXmlChildElementWithTagName (xml, child, "key")
  26. if (child->getAllSubText().trim() == key)
  27. return child->getNextElement();
  28. return nullptr;
  29. }
  30. static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1)
  31. {
  32. const XmlElement* const block = getElementForKey (xml, key);
  33. return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue;
  34. }
  35. // Get the track offsets for a CD given an XmlElement representing its TOC.Plist.
  36. // Returns NULL on success, otherwise a const char* representing an error.
  37. static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets)
  38. {
  39. const std::unique_ptr<XmlElement> xml (xmlDocument.getDocumentElement());
  40. if (xml == nullptr)
  41. return "Couldn't parse XML in file";
  42. const XmlElement* const dict = xml->getChildByName ("dict");
  43. if (dict == nullptr)
  44. return "Couldn't get top level dictionary";
  45. const XmlElement* const sessions = getElementForKey (*dict, "Sessions");
  46. if (sessions == nullptr)
  47. return "Couldn't find sessions key";
  48. const XmlElement* const session = sessions->getFirstChildElement();
  49. if (session == nullptr)
  50. return "Couldn't find first session";
  51. const int leadOut = getIntValueForKey (*session, "Leadout Block");
  52. if (leadOut < 0)
  53. return "Couldn't find Leadout Block";
  54. const XmlElement* const trackArray = getElementForKey (*session, "Track Array");
  55. if (trackArray == nullptr)
  56. return "Couldn't find Track Array";
  57. forEachXmlChildElement (*trackArray, track)
  58. {
  59. const int trackValue = getIntValueForKey (*track, "Start Block");
  60. if (trackValue < 0)
  61. return "Couldn't find Start Block in the track";
  62. offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200);
  63. }
  64. offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200);
  65. return nullptr;
  66. }
  67. static void findDevices (Array<File>& cds)
  68. {
  69. File volumes ("/Volumes");
  70. volumes.findChildFiles (cds, File::findDirectories, false);
  71. for (int i = cds.size(); --i >= 0;)
  72. if (! cds.getReference(i).getChildFile (".TOC.plist").exists())
  73. cds.remove (i);
  74. }
  75. struct TrackSorter
  76. {
  77. static int getCDTrackNumber (const File& file)
  78. {
  79. return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue();
  80. }
  81. static int compareElements (const File& first, const File& second)
  82. {
  83. const int firstTrack = getCDTrackNumber (first);
  84. const int secondTrack = getCDTrackNumber (second);
  85. jassert (firstTrack > 0 && secondTrack > 0);
  86. return firstTrack - secondTrack;
  87. }
  88. };
  89. }
  90. //==============================================================================
  91. StringArray AudioCDReader::getAvailableCDNames()
  92. {
  93. Array<File> cds;
  94. CDReaderHelpers::findDevices (cds);
  95. StringArray names;
  96. for (int i = 0; i < cds.size(); ++i)
  97. names.add (cds.getReference(i).getFileName());
  98. return names;
  99. }
  100. AudioCDReader* AudioCDReader::createReaderForCD (const int index)
  101. {
  102. Array<File> cds;
  103. CDReaderHelpers::findDevices (cds);
  104. if (cds[index].exists())
  105. return new AudioCDReader (cds[index]);
  106. return nullptr;
  107. }
  108. AudioCDReader::AudioCDReader (const File& volume)
  109. : AudioFormatReader (0, "CD Audio"),
  110. volumeDir (volume),
  111. currentReaderTrack (-1)
  112. {
  113. sampleRate = 44100.0;
  114. bitsPerSample = 16;
  115. numChannels = 2;
  116. usesFloatingPointData = false;
  117. refreshTrackLengths();
  118. }
  119. AudioCDReader::~AudioCDReader()
  120. {
  121. }
  122. void AudioCDReader::refreshTrackLengths()
  123. {
  124. tracks.clear();
  125. trackStartSamples.clear();
  126. lengthInSamples = 0;
  127. volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff");
  128. CDReaderHelpers::TrackSorter sorter;
  129. tracks.sort (sorter);
  130. const File toc (volumeDir.getChildFile (".TOC.plist"));
  131. if (toc.exists())
  132. {
  133. XmlDocument doc (toc);
  134. const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples);
  135. ignoreUnused (error); // could be logged..
  136. lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst();
  137. }
  138. }
  139. bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
  140. int64 startSampleInFile, int numSamples)
  141. {
  142. while (numSamples > 0)
  143. {
  144. int track = -1;
  145. for (int i = 0; i < trackStartSamples.size() - 1; ++i)
  146. {
  147. if (startSampleInFile < trackStartSamples.getUnchecked (i + 1))
  148. {
  149. track = i;
  150. break;
  151. }
  152. }
  153. if (track < 0)
  154. return false;
  155. if (track != currentReaderTrack)
  156. {
  157. reader = nullptr;
  158. if (FileInputStream* const in = tracks [track].createInputStream())
  159. {
  160. BufferedInputStream* const bin = new BufferedInputStream (in, 65536, true);
  161. AiffAudioFormat format;
  162. reader.reset (format.createReaderFor (bin, true));
  163. if (reader == nullptr)
  164. currentReaderTrack = -1;
  165. else
  166. currentReaderTrack = track;
  167. }
  168. }
  169. if (reader == nullptr)
  170. return false;
  171. const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track));
  172. const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos);
  173. reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable);
  174. numSamples -= numAvailable;
  175. startSampleInFile += numAvailable;
  176. }
  177. return true;
  178. }
  179. bool AudioCDReader::isCDStillPresent() const
  180. {
  181. return volumeDir.exists();
  182. }
  183. void AudioCDReader::ejectDisk()
  184. {
  185. JUCE_AUTORELEASEPOOL
  186. {
  187. [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())];
  188. }
  189. }
  190. bool AudioCDReader::isTrackAudio (int trackNum) const
  191. {
  192. return tracks [trackNum].hasFileExtension (".aiff");
  193. }
  194. void AudioCDReader::enableIndexScanning (bool)
  195. {
  196. // any way to do this on a Mac??
  197. }
  198. int AudioCDReader::getLastIndex() const
  199. {
  200. return 0;
  201. }
  202. Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/)
  203. {
  204. return {};
  205. }
  206. } // namespace juce