/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace CDReaderHelpers { inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) { forEachXmlChildElementWithTagName (xml, child, "key") if (child->getAllSubText().trim() == key) return child->getNextElement(); return nullptr; } static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1) { const XmlElement* const block = getElementForKey (xml, key); return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue; } // Get the track offsets for a CD given an XmlElement representing its TOC.Plist. // Returns NULL on success, otherwise a const char* representing an error. static const char* getTrackOffsets (XmlDocument& xmlDocument, Array& offsets) { const std::unique_ptr xml (xmlDocument.getDocumentElement()); if (xml == nullptr) return "Couldn't parse XML in file"; const XmlElement* const dict = xml->getChildByName ("dict"); if (dict == nullptr) return "Couldn't get top level dictionary"; const XmlElement* const sessions = getElementForKey (*dict, "Sessions"); if (sessions == nullptr) return "Couldn't find sessions key"; const XmlElement* const session = sessions->getFirstChildElement(); if (session == nullptr) return "Couldn't find first session"; const int leadOut = getIntValueForKey (*session, "Leadout Block"); if (leadOut < 0) return "Couldn't find Leadout Block"; const XmlElement* const trackArray = getElementForKey (*session, "Track Array"); if (trackArray == nullptr) return "Couldn't find Track Array"; forEachXmlChildElement (*trackArray, track) { const int trackValue = getIntValueForKey (*track, "Start Block"); if (trackValue < 0) return "Couldn't find Start Block in the track"; offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200); } offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200); return nullptr; } static void findDevices (Array& cds) { File volumes ("/Volumes"); volumes.findChildFiles (cds, File::findDirectories, false); for (int i = cds.size(); --i >= 0;) if (! cds.getReference(i).getChildFile (".TOC.plist").exists()) cds.remove (i); } struct TrackSorter { static int getCDTrackNumber (const File& file) { return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue(); } static int compareElements (const File& first, const File& second) { const int firstTrack = getCDTrackNumber (first); const int secondTrack = getCDTrackNumber (second); jassert (firstTrack > 0 && secondTrack > 0); return firstTrack - secondTrack; } }; } //============================================================================== StringArray AudioCDReader::getAvailableCDNames() { Array cds; CDReaderHelpers::findDevices (cds); StringArray names; for (int i = 0; i < cds.size(); ++i) names.add (cds.getReference(i).getFileName()); return names; } AudioCDReader* AudioCDReader::createReaderForCD (const int index) { Array cds; CDReaderHelpers::findDevices (cds); if (cds[index].exists()) return new AudioCDReader (cds[index]); return nullptr; } AudioCDReader::AudioCDReader (const File& volume) : AudioFormatReader (0, "CD Audio"), volumeDir (volume), currentReaderTrack (-1) { sampleRate = 44100.0; bitsPerSample = 16; numChannels = 2; usesFloatingPointData = false; refreshTrackLengths(); } AudioCDReader::~AudioCDReader() { } void AudioCDReader::refreshTrackLengths() { tracks.clear(); trackStartSamples.clear(); lengthInSamples = 0; volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff"); CDReaderHelpers::TrackSorter sorter; tracks.sort (sorter); const File toc (volumeDir.getChildFile (".TOC.plist")); if (toc.exists()) { XmlDocument doc (toc); const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples); ignoreUnused (error); // could be logged.. lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst(); } } bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { while (numSamples > 0) { int track = -1; for (int i = 0; i < trackStartSamples.size() - 1; ++i) { if (startSampleInFile < trackStartSamples.getUnchecked (i + 1)) { track = i; break; } } if (track < 0) return false; if (track != currentReaderTrack) { reader = nullptr; if (FileInputStream* const in = tracks [track].createInputStream()) { BufferedInputStream* const bin = new BufferedInputStream (in, 65536, true); AiffAudioFormat format; reader.reset (format.createReaderFor (bin, true)); if (reader == nullptr) currentReaderTrack = -1; else currentReaderTrack = track; } } if (reader == nullptr) return false; const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track)); const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos); reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable); numSamples -= numAvailable; startSampleInFile += numAvailable; } return true; } bool AudioCDReader::isCDStillPresent() const { return volumeDir.exists(); } void AudioCDReader::ejectDisk() { JUCE_AUTORELEASEPOOL { [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; } } bool AudioCDReader::isTrackAudio (int trackNum) const { return tracks [trackNum].hasFileExtension (".aiff"); } void AudioCDReader::enableIndexScanning (bool) { // any way to do this on a Mac?? } int AudioCDReader::getLastIndex() const { return 0; } Array AudioCDReader::findIndexesInTrack (const int /*trackNumber*/) { return {}; } } // namespace juce