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.

266 lines
7.8KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. [[maybe_unused]] const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples);
  134. lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst();
  135. }
  136. }
  137. bool AudioCDReader::readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
  138. int64 startSampleInFile, int numSamples)
  139. {
  140. while (numSamples > 0)
  141. {
  142. int track = -1;
  143. for (int i = 0; i < trackStartSamples.size() - 1; ++i)
  144. {
  145. if (startSampleInFile < trackStartSamples.getUnchecked (i + 1))
  146. {
  147. track = i;
  148. break;
  149. }
  150. }
  151. if (track < 0)
  152. return false;
  153. if (track != currentReaderTrack)
  154. {
  155. reader = nullptr;
  156. if (auto in = tracks [track].createInputStream())
  157. {
  158. BufferedInputStream* const bin = new BufferedInputStream (in.release(), 65536, true);
  159. AiffAudioFormat format;
  160. reader.reset (format.createReaderFor (bin, true));
  161. if (reader == nullptr)
  162. currentReaderTrack = -1;
  163. else
  164. currentReaderTrack = track;
  165. }
  166. }
  167. if (reader == nullptr)
  168. return false;
  169. const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track));
  170. const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos);
  171. reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable);
  172. numSamples -= numAvailable;
  173. startSampleInFile += numAvailable;
  174. }
  175. return true;
  176. }
  177. bool AudioCDReader::isCDStillPresent() const
  178. {
  179. return volumeDir.exists();
  180. }
  181. void AudioCDReader::ejectDisk()
  182. {
  183. JUCE_AUTORELEASEPOOL
  184. {
  185. [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())];
  186. }
  187. }
  188. bool AudioCDReader::isTrackAudio (int trackNum) const
  189. {
  190. return tracks [trackNum].hasFileExtension (".aiff");
  191. }
  192. void AudioCDReader::enableIndexScanning (bool)
  193. {
  194. // any way to do this on a Mac??
  195. }
  196. int AudioCDReader::getLastIndex() const
  197. {
  198. return 0;
  199. }
  200. Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/)
  201. {
  202. return {};
  203. }
  204. } // namespace juce