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.

267 lines
7.8KB

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