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.

263 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 CDReaderHelpers
  20. {
  21. inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key)
  22. {
  23. forEachXmlChildElementWithTagName (xml, child, "key")
  24. if (child->getAllSubText().trim() == key)
  25. return child->getNextElement();
  26. return nullptr;
  27. }
  28. static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1)
  29. {
  30. const XmlElement* const block = getElementForKey (xml, key);
  31. return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue;
  32. }
  33. // Get the track offsets for a CD given an XmlElement representing its TOC.Plist.
  34. // Returns NULL on success, otherwise a const char* representing an error.
  35. static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets)
  36. {
  37. const ScopedPointer<XmlElement> xml (xmlDocument.getDocumentElement());
  38. if (xml == nullptr)
  39. return "Couldn't parse XML in file";
  40. const XmlElement* const dict = xml->getChildByName ("dict");
  41. if (dict == nullptr)
  42. return "Couldn't get top level dictionary";
  43. const XmlElement* const sessions = getElementForKey (*dict, "Sessions");
  44. if (sessions == nullptr)
  45. return "Couldn't find sessions key";
  46. const XmlElement* const session = sessions->getFirstChildElement();
  47. if (session == nullptr)
  48. return "Couldn't find first session";
  49. const int leadOut = getIntValueForKey (*session, "Leadout Block");
  50. if (leadOut < 0)
  51. return "Couldn't find Leadout Block";
  52. const XmlElement* const trackArray = getElementForKey (*session, "Track Array");
  53. if (trackArray == nullptr)
  54. return "Couldn't find Track Array";
  55. forEachXmlChildElement (*trackArray, track)
  56. {
  57. const int trackValue = getIntValueForKey (*track, "Start Block");
  58. if (trackValue < 0)
  59. return "Couldn't find Start Block in the track";
  60. offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200);
  61. }
  62. offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200);
  63. return nullptr;
  64. }
  65. static void findDevices (Array<File>& cds)
  66. {
  67. File volumes ("/Volumes");
  68. volumes.findChildFiles (cds, File::findDirectories, false);
  69. for (int i = cds.size(); --i >= 0;)
  70. if (! cds.getReference(i).getChildFile (".TOC.plist").exists())
  71. cds.remove (i);
  72. }
  73. struct TrackSorter
  74. {
  75. static int getCDTrackNumber (const File& file)
  76. {
  77. return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue();
  78. }
  79. static int compareElements (const File& first, const File& second)
  80. {
  81. const int firstTrack = getCDTrackNumber (first);
  82. const int secondTrack = getCDTrackNumber (second);
  83. jassert (firstTrack > 0 && secondTrack > 0);
  84. return firstTrack - secondTrack;
  85. }
  86. };
  87. }
  88. //==============================================================================
  89. StringArray AudioCDReader::getAvailableCDNames()
  90. {
  91. Array<File> cds;
  92. CDReaderHelpers::findDevices (cds);
  93. StringArray names;
  94. for (int i = 0; i < cds.size(); ++i)
  95. names.add (cds.getReference(i).getFileName());
  96. return names;
  97. }
  98. AudioCDReader* AudioCDReader::createReaderForCD (const int index)
  99. {
  100. Array<File> cds;
  101. CDReaderHelpers::findDevices (cds);
  102. if (cds[index].exists())
  103. return new AudioCDReader (cds[index]);
  104. return nullptr;
  105. }
  106. AudioCDReader::AudioCDReader (const File& volume)
  107. : AudioFormatReader (0, "CD Audio"),
  108. volumeDir (volume),
  109. currentReaderTrack (-1)
  110. {
  111. sampleRate = 44100.0;
  112. bitsPerSample = 16;
  113. numChannels = 2;
  114. usesFloatingPointData = false;
  115. refreshTrackLengths();
  116. }
  117. AudioCDReader::~AudioCDReader()
  118. {
  119. }
  120. void AudioCDReader::refreshTrackLengths()
  121. {
  122. tracks.clear();
  123. trackStartSamples.clear();
  124. lengthInSamples = 0;
  125. volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff");
  126. CDReaderHelpers::TrackSorter sorter;
  127. tracks.sort (sorter);
  128. const File toc (volumeDir.getChildFile (".TOC.plist"));
  129. if (toc.exists())
  130. {
  131. XmlDocument doc (toc);
  132. const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples);
  133. ignoreUnused (error); // could be logged..
  134. lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst();
  135. }
  136. }
  137. bool AudioCDReader::readSamples (int** 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 (FileInputStream* const in = tracks [track].createInputStream())
  157. {
  158. BufferedInputStream* const bin = new BufferedInputStream (in, 65536, true);
  159. AiffAudioFormat format;
  160. reader = 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 Array<int>();
  203. }