/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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 { static const char* const aiffFormatName = "AIFF file"; //============================================================================== const char* const AiffAudioFormat::appleOneShot = "apple one shot"; const char* const AiffAudioFormat::appleRootSet = "apple root set"; const char* const AiffAudioFormat::appleRootNote = "apple root note"; const char* const AiffAudioFormat::appleBeats = "apple beats"; const char* const AiffAudioFormat::appleDenominator = "apple denominator"; const char* const AiffAudioFormat::appleNumerator = "apple numerator"; const char* const AiffAudioFormat::appleTag = "apple tag"; const char* const AiffAudioFormat::appleKey = "apple key"; //============================================================================== namespace AiffFileHelpers { inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); } #if JUCE_MSVC #pragma pack (push, 1) #endif //============================================================================== struct InstChunk { struct Loop { uint16 type; // these are different in AIFF and WAV uint16 startIdentifier; uint16 endIdentifier; } JUCE_PACKED; int8 baseNote; int8 detune; int8 lowNote; int8 highNote; int8 lowVelocity; int8 highVelocity; int16 gain; Loop sustainLoop; Loop releaseLoop; void copyTo (StringPairArray& values) const { values.set ("MidiUnityNote", String (baseNote)); values.set ("Detune", String (detune)); values.set ("LowNote", String (lowNote)); values.set ("HighNote", String (highNote)); values.set ("LowVelocity", String (lowVelocity)); values.set ("HighVelocity", String (highVelocity)); values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); } static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def) { return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue()); } static int8 getValue8 (const StringPairArray& values, const char* name, const char* def) { return (int8) values.getValue (name, def).getIntValue(); } static void create (MemoryBlock& block, const StringPairArray& values) { if (values.getAllKeys().contains ("MidiUnityNote", true)) { block.setSize ((sizeof (InstChunk) + 3) & ~(size_t) 3, true); auto& inst = *static_cast (block.getData()); inst.baseNote = getValue8 (values, "MidiUnityNote", "60"); inst.detune = getValue8 (values, "Detune", "0"); inst.lowNote = getValue8 (values, "LowNote", "0"); inst.highNote = getValue8 (values, "HighNote", "127"); inst.lowVelocity = getValue8 (values, "LowVelocity", "1"); inst.highVelocity = getValue8 (values, "HighVelocity", "127"); inst.gain = (int16) getValue16 (values, "Gain", "0"); inst.sustainLoop.type = getValue16 (values, "Loop0Type", "0"); inst.sustainLoop.startIdentifier = getValue16 (values, "Loop0StartIdentifier", "0"); inst.sustainLoop.endIdentifier = getValue16 (values, "Loop0EndIdentifier", "0"); inst.releaseLoop.type = getValue16 (values, "Loop1Type", "0"); inst.releaseLoop.startIdentifier = getValue16 (values, "Loop1StartIdentifier", "0"); inst.releaseLoop.endIdentifier = getValue16 (values, "Loop1EndIdentifier", "0"); } } } JUCE_PACKED; //============================================================================== struct BASCChunk { enum Key { minor = 1, major = 2, neither = 3, both = 4 }; BASCChunk (InputStream& input) { zerostruct (*this); flags = (uint32) input.readIntBigEndian(); numBeats = (uint32) input.readIntBigEndian(); rootNote = (uint16) input.readShortBigEndian(); key = (uint16) input.readShortBigEndian(); timeSigNum = (uint16) input.readShortBigEndian(); timeSigDen = (uint16) input.readShortBigEndian(); oneShot = (uint16) input.readShortBigEndian(); input.read (unknown, sizeof (unknown)); } void addToMetadata (StringPairArray& metadata) const { const bool rootNoteSet = rootNote != 0; setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2); setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); if (rootNoteSet) metadata.set (AiffAudioFormat::appleRootNote, String (rootNote)); metadata.set (AiffAudioFormat::appleBeats, String (numBeats)); metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen)); metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum)); const char* keyString = nullptr; switch (key) { case minor: keyString = "minor"; break; case major: keyString = "major"; break; case neither: keyString = "neither"; break; case both: keyString = "both"; break; default: break; } if (keyString != nullptr) metadata.set (AiffAudioFormat::appleKey, keyString); } void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const { values.set (name, shouldBeSet ? "1" : "0"); } uint32 flags; uint32 numBeats; uint16 rootNote; uint16 key; uint16 timeSigNum; uint16 timeSigDen; uint16 oneShot; uint8 unknown[66]; } JUCE_PACKED; #if JUCE_MSVC #pragma pack (pop) #endif //============================================================================== namespace CATEChunk { static bool isValidTag (const char* d) noexcept { return CharacterFunctions::isLetterOrDigit (d[0]) && CharacterFunctions::isUpperCase (static_cast (d[0])) && CharacterFunctions::isLetterOrDigit (d[1]) && CharacterFunctions::isLowerCase (static_cast (d[1])) && CharacterFunctions::isLetterOrDigit (d[2]) && CharacterFunctions::isLowerCase (static_cast (d[2])); } static bool isAppleGenre (const String& tag) noexcept { static const char* appleGenres[] = { "Rock/Blues", "Electronic/Dance", "Jazz", "Urban", "World/Ethnic", "Cinematic/New Age", "Orchestral", "Country/Folk", "Experimental", "Other Genre" }; for (int i = 0; i < numElementsInArray (appleGenres); ++i) if (tag == appleGenres[i]) return true; return false; } static String read (InputStream& input, const uint32 length) { MemoryBlock mb; input.skipNextBytes (4); input.readIntoMemoryBlock (mb, (ssize_t) length - 4); StringArray tagsArray; auto* data = static_cast (mb.getData()); auto* dataEnd = data + mb.getSize(); while (data < dataEnd) { bool isGenre = false; if (isValidTag (data)) { auto tag = String (CharPointer_UTF8 (data), CharPointer_UTF8 (dataEnd)); isGenre = isAppleGenre (tag); tagsArray.add (tag); } data += isGenre ? 118 : 50; if (data < dataEnd && data[0] == 0) { if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50; else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118; else if (data + 170 < dataEnd && isValidTag (data + 168)) data += 168; } } return tagsArray.joinIntoString (";"); } } //============================================================================== namespace MarkChunk { static bool metaDataContainsZeroIdentifiers (const StringPairArray& values) { // (zero cue identifiers are valid for WAV but not for AIFF) const String cueString ("Cue"); const String noteString ("CueNote"); const String identifierString ("Identifier"); for (auto& key : values.getAllKeys()) { if (key.startsWith (noteString)) continue; // zero identifier IS valid in a COMT chunk if (key.startsWith (cueString) && key.contains (identifierString)) if (values.getValue (key, "-1").getIntValue() == 0) return true; } return false; } static void create (MemoryBlock& block, const StringPairArray& values) { auto numCues = values.getValue ("NumCuePoints", "0").getIntValue(); if (numCues > 0) { MemoryOutputStream out (block, false); out.writeShortBigEndian ((short) numCues); auto numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue(); auto idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF #if JUCE_DEBUG Array identifiers; #endif for (int i = 0; i < numCues; ++i) { auto prefixCue = "Cue" + String (i); auto identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue(); #if JUCE_DEBUG jassert (! identifiers.contains (identifier)); identifiers.add (identifier); #endif auto offset = values.getValue (prefixCue + "Offset", "0").getIntValue(); auto label = "CueLabel" + String (i); for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex) { auto prefixLabel = "CueLabel" + String (labelIndex); auto labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue(); if (labelIdentifier == identifier) { label = values.getValue (prefixLabel + "Text", label); break; } } out.writeShortBigEndian ((short) identifier); out.writeIntBigEndian (offset); auto labelLength = jmin ((size_t) 254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring out.writeByte (static_cast (labelLength + 1)); out.write (label.toUTF8(), labelLength); out.writeByte (0); if ((out.getDataSize() & 1) != 0) out.writeByte (0); } } } } //============================================================================== namespace COMTChunk { static void create (MemoryBlock& block, const StringPairArray& values) { auto numNotes = values.getValue ("NumCueNotes", "0").getIntValue(); if (numNotes > 0) { MemoryOutputStream out (block, false); out.writeShortBigEndian ((short) numNotes); for (int i = 0; i < numNotes; ++i) { auto prefix = "CueNote" + String (i); out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue()); out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue()); auto comment = values.getValue (prefix + "Text", String()); auto commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534); out.writeShortBigEndian (static_cast (commentLength + 1)); out.write (comment.toUTF8(), commentLength); out.writeByte (0); if ((out.getDataSize() & 1) != 0) out.writeByte (0); } } } } } //============================================================================== class AiffAudioFormatReader : public AudioFormatReader { public: AiffAudioFormatReader (InputStream* in) : AudioFormatReader (in, aiffFormatName) { using namespace AiffFileHelpers; if (input->readInt() == chunkName ("FORM")) { auto len = input->readIntBigEndian(); auto end = input->getPosition() + len; auto nextType = input->readInt(); if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC")) { bool hasGotVer = false; bool hasGotData = false; bool hasGotType = false; while (input->getPosition() < end) { auto type = input->readInt(); auto length = (uint32) input->readIntBigEndian(); auto chunkEnd = input->getPosition() + length; if (type == chunkName ("FVER")) { hasGotVer = true; auto ver = input->readIntBigEndian(); if (ver != 0 && ver != (int) 0xa2805140) break; } else if (type == chunkName ("COMM")) { hasGotType = true; numChannels = (unsigned int) input->readShortBigEndian(); lengthInSamples = input->readIntBigEndian(); bitsPerSample = (unsigned int) input->readShortBigEndian(); bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3); unsigned char sampleRateBytes[10]; input->read (sampleRateBytes, 10); const int byte0 = sampleRateBytes[0]; if ((byte0 & 0x80) != 0 || byte0 <= 0x3F || byte0 > 0x40 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C)) break; auto sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2); sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes)); sampleRate = (int) sampRate; if (length <= 18) { // some types don't have a chunk large enough to include a compression // type, so assume it's just big-endian pcm littleEndian = false; } else { auto compType = input->readInt(); if (compType == chunkName ("NONE") || compType == chunkName ("twos")) { littleEndian = false; } else if (compType == chunkName ("sowt")) { littleEndian = true; } else if (compType == chunkName ("fl32") || compType == chunkName ("FL32")) { littleEndian = false; usesFloatingPointData = true; } else { sampleRate = 0; break; } } } else if (type == chunkName ("SSND")) { hasGotData = true; auto offset = input->readIntBigEndian(); dataChunkStart = input->getPosition() + 4 + offset; lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0; } else if (type == chunkName ("MARK")) { auto numCues = (uint16) input->readShortBigEndian(); // these two are always the same for AIFF-read files metadataValues.set ("NumCuePoints", String (numCues)); metadataValues.set ("NumCueLabels", String (numCues)); for (uint16 i = 0; i < numCues; ++i) { auto identifier = (uint16) input->readShortBigEndian(); auto offset = (uint32) input->readIntBigEndian(); auto stringLength = (uint8) input->readByte(); MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength); // if the stringLength is even then read one more byte as the // string needs to be an even number of bytes INCLUDING the // leading length character in the pascal string if ((stringLength & 1) == 0) input->readByte(); auto prefixCue = "Cue" + String (i); metadataValues.set (prefixCue + "Identifier", String (identifier)); metadataValues.set (prefixCue + "Offset", String (offset)); auto prefixLabel = "CueLabel" + String (i); metadataValues.set (prefixLabel + "Identifier", String (identifier)); metadataValues.set (prefixLabel + "Text", textBlock.toString()); } } else if (type == chunkName ("COMT")) { auto numNotes = (uint16) input->readShortBigEndian(); metadataValues.set ("NumCueNotes", String (numNotes)); for (uint16 i = 0; i < numNotes; ++i) { auto timestamp = (uint32) input->readIntBigEndian(); auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case auto stringLength = (uint16) input->readShortBigEndian(); MemoryBlock textBlock; input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); auto prefix = "CueNote" + String (i); metadataValues.set (prefix + "TimeStamp", String (timestamp)); metadataValues.set (prefix + "Identifier", String (identifier)); metadataValues.set (prefix + "Text", textBlock.toString()); } } else if (type == chunkName ("INST")) { HeapBlock inst; inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); input->read (inst, (int) length); inst->copyTo (metadataValues); } else if (type == chunkName ("basc")) { AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues); } else if (type == chunkName ("cate")) { metadataValues.set (AiffAudioFormat::appleTag, AiffFileHelpers::CATEChunk::read (*input, length)); } else if ((hasGotVer && hasGotData && hasGotType) || chunkEnd < input->getPosition() || input->isExhausted()) { break; } input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address) } } } if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "AIFF"); } //============================================================================== bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override { clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, startSampleInFile, numSamples, lengthInSamples); if (numSamples <= 0) return true; input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame); while (numSamples > 0) { const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3) char tempBuffer [tempBufSize]; const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples); const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame); if (bytesRead < numThisTime * bytesPerFrame) { jassert (bytesRead >= 0); zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead)); } if (littleEndian) copySampleData (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); else copySampleData (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; } return true; } template static void copySampleData (unsigned int numBitsPerSample, bool floatingPointData, int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels, const void* sourceData, int numberOfChannels, int numSamples) noexcept { switch (numBitsPerSample) { case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break; case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break; case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break; case 32: if (floatingPointData) ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); else ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break; default: jassertfalse; break; } } int bytesPerFrame; int64 dataChunkStart; bool littleEndian; private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader) }; //============================================================================== class AiffAudioFormatWriter : public AudioFormatWriter { public: AiffAudioFormatWriter (OutputStream* out, double rate, unsigned int numChans, unsigned int bits, const StringPairArray& metadataValues) : AudioFormatWriter (out, aiffFormatName, rate, numChans, bits) { using namespace AiffFileHelpers; if (metadataValues.size() > 0) { // The meta data should have been sanitised for the AIFF format. // If it was originally sourced from a WAV file the MetaDataSource // key should be removed (or set to "AIFF") once this has been done jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV"); MarkChunk::create (markChunk, metadataValues); COMTChunk::create (comtChunk, metadataValues); InstChunk::create (instChunk, metadataValues); } headerPosition = out->getPosition(); writeHeader(); } ~AiffAudioFormatWriter() override { if ((bytesWritten & 1) != 0) output->writeByte (0); writeHeader(); } //============================================================================== bool write (const int** data, int numSamples) override { jassert (numSamples >= 0); jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! if (writeFailed) return false; auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); switch (bitsPerSample) { case 8: WriteHelper::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; case 16: WriteHelper::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; case 24: WriteHelper::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; case 32: WriteHelper::write (tempBlock.getData(), (int) numChannels, data, numSamples); break; default: jassertfalse; break; } if (bytesWritten + bytes >= (size_t) 0xfff00000 || ! output->write (tempBlock.getData(), bytes)) { // failed to write to disk, so let's try writing the header. // If it's just run out of disk space, then if it does manage // to write the header, we'll still have a useable file.. writeHeader(); writeFailed = true; return false; } bytesWritten += bytes; lengthInSamples += (uint64) numSamples; return true; } private: MemoryBlock tempBlock, markChunk, comtChunk, instChunk; uint64 lengthInSamples = 0, bytesWritten = 0; int64 headerPosition = 0; bool writeFailed = false; void writeHeader() { using namespace AiffFileHelpers; const bool couldSeekOk = output->setPosition (headerPosition); ignoreUnused (couldSeekOk); // if this fails, you've given it an output stream that can't seek! It needs // to be able to seek back to write the header jassert (couldSeekOk); auto headerLen = (int) (54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0) + (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0) + (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0)); auto audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8)); audioBytes += (audioBytes & 1); output->writeInt (chunkName ("FORM")); output->writeIntBigEndian (headerLen + audioBytes - 8); output->writeInt (chunkName ("AIFF")); output->writeInt (chunkName ("COMM")); output->writeIntBigEndian (18); output->writeShortBigEndian ((short) numChannels); output->writeIntBigEndian ((int) lengthInSamples); output->writeShortBigEndian ((short) bitsPerSample); uint8 sampleRateBytes[10] = {}; if (sampleRate <= 1) { sampleRateBytes[0] = 0x3f; sampleRateBytes[1] = 0xff; sampleRateBytes[2] = 0x80; } else { int mask = 0x40000000; sampleRateBytes[0] = 0x40; if (sampleRate >= mask) { jassertfalse; sampleRateBytes[1] = 0x1d; } else { int n = (int) sampleRate; int i; for (i = 0; i <= 32 ; ++i) { if ((n & mask) != 0) break; mask >>= 1; } n = n << (i + 1); sampleRateBytes[1] = (uint8) (29 - i); sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff); sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff); sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff); sampleRateBytes[5] = (uint8) (n & 0xff); } } output->write (sampleRateBytes, 10); if (markChunk.getSize() > 0) { output->writeInt (chunkName ("MARK")); output->writeIntBigEndian ((int) markChunk.getSize()); *output << markChunk; } if (comtChunk.getSize() > 0) { output->writeInt (chunkName ("COMT")); output->writeIntBigEndian ((int) comtChunk.getSize()); *output << comtChunk; } if (instChunk.getSize() > 0) { output->writeInt (chunkName ("INST")); output->writeIntBigEndian ((int) instChunk.getSize()); *output << instChunk; } output->writeInt (chunkName ("SSND")); output->writeIntBigEndian (audioBytes + 8); output->writeInt (0); output->writeInt (0); jassert (output->getPosition() == headerLen); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter) }; //============================================================================== class MemoryMappedAiffReader : public MemoryMappedAudioFormatReader { public: MemoryMappedAiffReader (const File& f, const AiffAudioFormatReader& reader) : MemoryMappedAudioFormatReader (f, reader, reader.dataChunkStart, reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame), littleEndian (reader.littleEndian) { } bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override { clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, startSampleInFile, numSamples, lengthInSamples); if (map == nullptr || ! mappedSection.contains (Range (startSampleInFile, startSampleInFile + numSamples))) { jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read. return false; } if (littleEndian) AiffAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples); else AiffAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples); return true; } void getSample (int64 sample, float* result) const noexcept override { auto num = (int) numChannels; if (map == nullptr || ! mappedSection.contains (sample)) { jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read. zeromem (result, (size_t) num * sizeof (float)); return; } float** dest = &result; const void* source = sampleToPointer (sample); if (littleEndian) { switch (bitsPerSample) { case 8: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 16: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 24: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 32: if (usesFloatingPointData) ReadHelper::read (dest, 0, 1, source, 1, num); else ReadHelper::read (dest, 0, 1, source, 1, num); break; default: jassertfalse; break; } } else { switch (bitsPerSample) { case 8: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 16: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 24: ReadHelper::read (dest, 0, 1, source, 1, num); break; case 32: if (usesFloatingPointData) ReadHelper::read (dest, 0, 1, source, 1, num); else ReadHelper::read (dest, 0, 1, source, 1, num); break; default: jassertfalse; break; } } } void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range* results, int numChannelsToRead) override { numSamples = jmin (numSamples, lengthInSamples - startSampleInFile); if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range (startSampleInFile, startSampleInFile + numSamples))) { jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read. for (int i = 0; i < numChannelsToRead; ++i) results[i] = Range(); return; } switch (bitsPerSample) { case 8: scanMinAndMax (startSampleInFile, numSamples, results, numChannelsToRead); break; case 16: scanMinAndMax (startSampleInFile, numSamples, results, numChannelsToRead); break; case 24: scanMinAndMax (startSampleInFile, numSamples, results, numChannelsToRead); break; case 32: if (usesFloatingPointData) scanMinAndMax (startSampleInFile, numSamples, results, numChannelsToRead); else scanMinAndMax (startSampleInFile, numSamples, results, numChannelsToRead); break; default: jassertfalse; break; } } using AudioFormatReader::readMaxLevels; private: const bool littleEndian; template void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range* results, int numChannelsToRead) const noexcept { for (int i = 0; i < numChannelsToRead; ++i) results[i] = scanMinAndMaxForChannel (i, startSampleInFile, numSamples); } template Range scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept { return littleEndian ? scanMinAndMaxInterleaved (channel, startSampleInFile, numSamples) : scanMinAndMaxInterleaved (channel, startSampleInFile, numSamples); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader) }; //============================================================================== AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {} AiffAudioFormat::~AiffAudioFormat() {} Array AiffAudioFormat::getPossibleSampleRates() { return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; } Array AiffAudioFormat::getPossibleBitDepths() { return { 8, 16, 24 }; } bool AiffAudioFormat::canDoStereo() { return true; } bool AiffAudioFormat::canDoMono() { return true; } #if JUCE_MAC bool AiffAudioFormat::canHandleFile (const File& f) { if (AudioFormat::canHandleFile (f)) return true; auto type = f.getMacOSType(); // (NB: written as hex to avoid four-char-constant warnings) return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */ || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */; } #endif AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) { std::unique_ptr w (new AiffAudioFormatReader (sourceStream)); if (w->sampleRate > 0 && w->numChannels > 0) return w.release(); if (! deleteStreamIfOpeningFails) w->input = nullptr; return nullptr; } MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (const File& file) { return createMemoryMappedReader (file.createInputStream().release()); } MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (FileInputStream* fin) { if (fin != nullptr) { AiffAudioFormatReader reader (fin); if (reader.lengthInSamples > 0) return new MemoryMappedAiffReader (fin->getFile(), reader); } return nullptr; } AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int /*qualityOptionIndex*/) { if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, (unsigned int) bitsPerSample, metadataValues); return nullptr; } } // namespace juce