diff --git a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index bf5142d7da..982242b9d2 100644 --- a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -407,16 +407,8 @@ public: bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { - const int64 samplesAvailable = lengthInSamples - startSampleInFile; - - if (samplesAvailable < numSamples) - { - for (int i = numDestChannels; --i >= 0;) - if (destSamples[i] != nullptr) - zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); - - numSamples = (int) samplesAvailable; - } + clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, lengthInSamples); if (numSamples <= 0) return true; @@ -440,27 +432,14 @@ public: jassert (! usesFloatingPointData); // (would need to add support for this if it's possible) if (littleEndian) - { - switch (bitsPerSample) - { - case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 32: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - default: jassertfalse; break; - } - } + copySampleData (bitsPerSample, usesFloatingPointData, + destSamples, startOffsetInDestBuffer, numDestChannels, + tempBuffer, (int) numChannels, numThisTime); else - { - switch (bitsPerSample) - { - case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 32: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - default: jassertfalse; break; - } - } + copySampleData (bitsPerSample, usesFloatingPointData, + destSamples, startOffsetInDestBuffer, numDestChannels, + tempBuffer, (int) numChannels, numThisTime); + startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; @@ -469,6 +448,21 @@ public: return true; } + template + static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData, + int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels, + const void* sourceData, int numChannels, int numSamples) noexcept + { + switch (bitsPerSample) + { + case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 32: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + default: jassertfalse; break; + } + } + int bytesPerFrame; int64 dataChunkStart; bool littleEndian; @@ -661,6 +655,101 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter) }; +//============================================================================== +class MemoryMappedAiffReader : public MemoryMappedAudioFormatReader +{ +public: + MemoryMappedAiffReader (const File& file, const AiffAudioFormatReader& reader) + : MemoryMappedAudioFormatReader (file, reader, reader.dataChunkStart, + reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame), + littleEndian (reader.littleEndian) + { + } + + bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, + int64 startSampleInFile, int numSamples) + { + 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 readMaxLevels (int64 startSampleInFile, int64 numSamples, + float& min0, float& max0, float& min1, float& max1) + { + if (numSamples <= 0) + { + min0 = max0 = min1 = max1 = 0; + return; + } + + 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. + + min0 = max0 = min1 = max1 = 0; + return; + } + + switch (bitsPerSample) + { + case 8: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 16: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 24: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 32: if (usesFloatingPointData) scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); + else scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + default: jassertfalse; break; + } + } + +private: + const bool littleEndian; + + template + void scanMinAndMax (int64 startSampleInFile, int64 numSamples, + float& min0, float& max0, float& min1, float& max1) const noexcept + { + if (littleEndian) + scanMinAndMax2 (startSampleInFile, numSamples, min0, max0, min1, max1); + else + scanMinAndMax2 (startSampleInFile, numSamples, min0, max0, min1, max1); + } + + template + void scanMinAndMax2 (int64 startSampleInFile, int64 numSamples, + float& min0, float& max0, float& min1, float& max1) const noexcept + { + typedef AudioData::Pointer SourceType; + + SourceType (sampleToPointer (startSampleInFile), (int) numChannels) + .findMinAndMax ((size_t) numSamples, min0, max0); + + if (numChannels > 1) + SourceType (addBytesToPointer (sampleToPointer (startSampleInFile), bitsPerSample / 8), (int) numChannels) + .findMinAndMax ((size_t) numSamples, min1, max1); + else + min1 = max1 = 0; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader) +}; + //============================================================================== AiffAudioFormat::AiffAudioFormat() : AudioFormat (TRANS (aiffFormatName), StringArray (aiffExtensions)) @@ -711,6 +800,19 @@ AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, return nullptr; } +MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (const File& file) +{ + if (FileInputStream* fin = file.createInputStream()) + { + AiffAudioFormatReader reader (fin); + + if (reader.lengthInSamples > 0) + return new MemoryMappedAiffReader (file, reader); + } + + return nullptr; +} + AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numberOfChannels, diff --git a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h index 53fa1bbc54..4893a1aceb 100644 --- a/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h @@ -53,6 +53,8 @@ public: AudioFormatReader* createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails); + MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&); + AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, diff --git a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index 75e4ef8ef7..042db39539 100644 --- a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -123,17 +123,8 @@ public: bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { - jassert (destSamples != nullptr); - const int64 samplesAvailable = lengthInSamples - startSampleInFile; - - if (samplesAvailable < numSamples) - { - for (int i = numDestChannels; --i >= 0;) - if (destSamples[i] != nullptr) - zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); - - numSamples = (int) samplesAvailable; - } + clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, lengthInSamples); if (numSamples <= 0) return true; diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index 7bddad76f8..5232d48aba 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -712,17 +712,8 @@ public: bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { - jassert (destSamples != nullptr); - const int64 samplesAvailable = lengthInSamples - startSampleInFile; - - if (samplesAvailable < numSamples) - { - for (int i = numDestChannels; --i >= 0;) - if (destSamples[i] != nullptr) - zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); - - numSamples = (int) samplesAvailable; - } + clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, lengthInSamples); if (numSamples <= 0) return true; @@ -743,15 +734,9 @@ public: zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead)); } - switch (bitsPerSample) - { - case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - case 32: if (usesFloatingPointData) ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); - else ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; - default: jassertfalse; break; - } + copySampleData (bitsPerSample, usesFloatingPointData, + destSamples, startOffsetInDestBuffer, numDestChannels, + tempBuffer, (int) numChannels, numThisTime); startOffsetInDestBuffer += numThisTime; numSamples -= numThisTime; @@ -760,14 +745,27 @@ public: return true; } - int64 bwavChunkStart, bwavSize; + static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData, + int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels, + const void* sourceData, int numChannels, int numSamples) noexcept + { + switch (bitsPerSample) + { + case 8: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 16: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 24: ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + case 32: if (usesFloatingPointData) ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); + else ReadHelper::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; + default: jassertfalse; break; + } + } -private: - ScopedPointer converter; - int bytesPerFrame; + int64 bwavChunkStart, bwavSize; int64 dataChunkStart, dataLength; + int bytesPerFrame; bool isRF64; +private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader) }; @@ -853,7 +851,6 @@ public: } private: - ScopedPointer converter; MemoryBlock tempBlock, bwavChunk, smplChunk, instChunk, cueChunk, listChunk; uint64 lengthInSamples, bytesWritten; int64 headerPosition; @@ -1001,6 +998,82 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter) }; +//============================================================================== +class MemoryMappedWavReader : public MemoryMappedAudioFormatReader +{ +public: + MemoryMappedWavReader (const File& file, const WavAudioFormatReader& reader) + : MemoryMappedAudioFormatReader (file, reader, reader.dataChunkStart, + reader.dataLength, reader.bytesPerFrame) + { + } + + bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, + int64 startSampleInFile, int numSamples) + { + 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; + } + + WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData, + destSamples, startOffsetInDestBuffer, numDestChannels, + sampleToPointer (startSampleInFile), (int) numChannels, numSamples); + return true; + } + + void readMaxLevels (int64 startSampleInFile, int64 numSamples, + float& min0, float& max0, float& min1, float& max1) + { + if (numSamples <= 0) + { + min0 = max0 = min1 = max1 = 0; + return; + } + + 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. + + min0 = max0 = min1 = max1 = 0; + return; + } + + switch (bitsPerSample) + { + case 8: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 16: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 24: scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + case 32: if (usesFloatingPointData) scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); + else scanMinAndMax (startSampleInFile, numSamples, min0, max0, min1, max1); break; + default: jassertfalse; break; + } + } + +private: + template + void scanMinAndMax (int64 startSampleInFile, int64 numSamples, + float& min0, float& max0, float& min1, float& max1) const + { + typedef AudioData::Pointer SourceType; + + SourceType (sampleToPointer (startSampleInFile), (int) numChannels) + .findMinAndMax ((size_t) numSamples, min0, max0); + + if (numChannels > 1) + SourceType (addBytesToPointer (sampleToPointer (startSampleInFile), bitsPerSample / 8), (int) numChannels) + .findMinAndMax ((size_t) numSamples, min1, max1); + else + min1 = max1 = 0; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader) +}; + //============================================================================== WavAudioFormat::WavAudioFormat() : AudioFormat (TRANS (wavFormatName), StringArray (wavExtensions)) @@ -1040,6 +1113,19 @@ AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, return nullptr; } +MemoryMappedAudioFormatReader* WavAudioFormat::createMemoryMappedReader (const File& file) +{ + if (FileInputStream* fin = file.createInputStream()) + { + WavAudioFormatReader reader (fin); + + if (reader.lengthInSamples > 0) + return new MemoryMappedWavReader (file, reader); + } + + return nullptr; +} + AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, unsigned int numChannels, int bitsPerSample, const StringPairArray& metadataValues, int /*qualityOptionIndex*/) diff --git a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h index c75e17b0db..45b569a377 100644 --- a/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h +++ b/modules/juce_audio_formats/codecs/juce_WavAudioFormat.h @@ -120,6 +120,8 @@ public: AudioFormatReader* createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails); + MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file); + AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, diff --git a/modules/juce_audio_formats/format/juce_AudioFormat.cpp b/modules/juce_audio_formats/format/juce_AudioFormat.cpp index b5d2b3cd76..93ebdb8a79 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormat.cpp +++ b/modules/juce_audio_formats/format/juce_AudioFormat.cpp @@ -47,3 +47,8 @@ const String& AudioFormat::getFormatName() const { return formatN const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } bool AudioFormat::isCompressed() { return false; } StringArray AudioFormat::getQualityOptions() { return StringArray(); } + +MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (const File&) +{ + return nullptr; +} diff --git a/modules/juce_audio_formats/format/juce_AudioFormat.h b/modules/juce_audio_formats/format/juce_AudioFormat.h index 7c34b7a0d0..94320a2903 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormat.h +++ b/modules/juce_audio_formats/format/juce_AudioFormat.h @@ -28,7 +28,7 @@ #include "juce_AudioFormatReader.h" #include "juce_AudioFormatWriter.h" - +#include "juce_MemoryMappedAudioFormatReader.h" //============================================================================== /** @@ -113,6 +113,13 @@ public: virtual AudioFormatReader* createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) = 0; + /** Attempts to create a MemoryMappedAudioFormatReader, if possible for this + format. + + If the format does not support this, the method will return nullptr; + */ + virtual MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file); + /** Tries to create an object that can write to a stream with this audio format. The writer object that is returned can be used to write to the stream, and diff --git a/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp b/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp index dea1455465..5b84c21a3d 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp +++ b/modules/juce_audio_formats/format/juce_AudioFormatReader.cpp @@ -23,15 +23,14 @@ ============================================================================== */ -AudioFormatReader::AudioFormatReader (InputStream* const in, - const String& formatName_) +AudioFormatReader::AudioFormatReader (InputStream* const in, const String& name) : sampleRate (0), bitsPerSample (0), lengthInSamples (0), numChannels (0), usesFloatingPointData (false), input (in), - formatName (formatName_) + formatName (name) { } @@ -365,3 +364,56 @@ int64 AudioFormatReader::searchForLevel (int64 startSample, return -1; } + +//============================================================================== +MemoryMappedAudioFormatReader::MemoryMappedAudioFormatReader (const File& f, const AudioFormatReader& reader, + int64 start, int64 length, int frameSize) + : AudioFormatReader (nullptr, reader.getFormatName()), file (f), + dataChunkStart (start), dataLength (length), bytesPerFrame (frameSize) +{ + sampleRate = reader.sampleRate; + bitsPerSample = reader.bitsPerSample; + lengthInSamples = reader.lengthInSamples; + numChannels = reader.numChannels; + metadataValues = reader.metadataValues; + usesFloatingPointData = reader.usesFloatingPointData; +} + +bool MemoryMappedAudioFormatReader::mapEntireFile() +{ + return mapSectionOfFile (Range (0, lengthInSamples)); +} + +bool MemoryMappedAudioFormatReader::mapSectionOfFile (const Range& samplesToMap) +{ + if (map == nullptr || samplesToMap != mappedSection) + { + map = nullptr; + + const Range fileRange (sampleToFilePos (samplesToMap.getStart()), + sampleToFilePos (samplesToMap.getEnd())); + + map = new MemoryMappedFile (file, fileRange, MemoryMappedFile::readOnly); + + if (map->getData() == nullptr) + map = nullptr; + else + mappedSection = Range (jmax ((int64) 0, filePosToSample (map->getRange().getStart())), + jmin (lengthInSamples, filePosToSample (map->getRange().getEnd()))); + } + + return map != nullptr; +} + +void MemoryMappedAudioFormatReader::touchSample (int64 sample) const noexcept +{ + if (map != nullptr && mappedSection.contains (sample)) + { + static int dummy = 0; // to force the compiler not to optimise this stuff away + dummy += *(int*) sampleToPointer (sample); + } + else + { + jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read. + } +} diff --git a/modules/juce_audio_formats/format/juce_AudioFormatReader.h b/modules/juce_audio_formats/format/juce_AudioFormatReader.h index af990f3200..4c762c8427 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormatReader.h +++ b/modules/juce_audio_formats/format/juce_AudioFormatReader.h @@ -237,13 +237,15 @@ protected: typedef AudioData::Pointer DestType; typedef AudioData::Pointer SourceType; - static void read (int** destData, int destOffset, int numDestChannels, const void* sourceData, int numSourceChannels, int numSamples) noexcept + template + static void read (TargetType* const* destData, int destOffset, int numDestChannels, + const void* sourceData, int numSourceChannels, int numSamples) noexcept { for (int i = 0; i < numDestChannels; ++i) { - if (destData[i] != nullptr) + if (void* targetChan = destData[i]) { - DestType dest (destData[i]); + DestType dest (targetChan); dest += destOffset; if (i < numSourceChannels) @@ -255,6 +257,26 @@ protected: } }; + /** Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie + beyond the end of their available length. + */ + static void clearSamplesBeyondAvailableLength (int** destSamples, int numDestChannels, + int startOffsetInDestBuffer, int64 startSampleInFile, + int& numSamples, int64 fileLengthInSamples) + { + jassert (destSamples != nullptr); + const int64 samplesAvailable = fileLengthInSamples - startSampleInFile; + + if (samplesAvailable < numSamples) + { + for (int i = numDestChannels; --i >= 0;) + if (destSamples[i] != nullptr) + zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); + + numSamples = (int) samplesAvailable; + } + } + private: String formatName; diff --git a/modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp b/modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp index 73323fc9af..34d660200e 100644 --- a/modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp +++ b/modules/juce_audio_formats/format/juce_AudioSubsectionReader.cpp @@ -51,17 +51,8 @@ AudioSubsectionReader::~AudioSubsectionReader() bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) { - if (startSampleInFile + numSamples > length) - { - for (int i = numDestChannels; --i >= 0;) - if (destSamples[i] != nullptr) - zeromem (destSamples[i], sizeof (int) * (size_t) numSamples); - - numSamples = jmin (numSamples, (int) (length - startSampleInFile)); - - if (numSamples <= 0) - return true; - } + clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, length); return source->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startSampleInFile + startSample, numSamples); diff --git a/modules/juce_audio_formats/format/juce_MemoryMappedAudioFormatReader.h b/modules/juce_audio_formats/format/juce_MemoryMappedAudioFormatReader.h new file mode 100644 index 0000000000..70016a3028 --- /dev/null +++ b/modules/juce_audio_formats/format/juce_MemoryMappedAudioFormatReader.h @@ -0,0 +1,91 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-11 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_MEMORYMAPPEDAUDIOFORMATREADER_JUCEHEADER__ +#define __JUCE_MEMORYMAPPEDAUDIOFORMATREADER_JUCEHEADER__ + +//============================================================================== +/** + A specialised type of AudioFormatReader that uses a MemoryMappedFile to read + directly from an audio file. + + This allows for incredibly fast random-access to sample data in the mapped + region of the file, but not all audio formats support it - see + AudioFormat::createMemoryMappedReader(). + + Note that before reading samples from a MemoryMappedAudioFormatReader, you must first + call mapEntireFile() or mapSectionOfFile() to ensure that the region you want to + read has been mapped. + + @see AudioFormat::createMemoryMappedReader, AudioFormatReader +*/ +class JUCE_API MemoryMappedAudioFormatReader : public AudioFormatReader +{ +protected: + //============================================================================== + /** Creates an MemoryMappedAudioFormatReader object. + + Note that before attempting to read any data, you must call mapEntireFile() + or mapSectionOfFile() to ensure that the region you want to read has + been mapped. + */ + MemoryMappedAudioFormatReader (const File& file, const AudioFormatReader& details, + int64 dataChunkStart, int64 dataChunkLength, int bytesPerFrame); + +public: + /** Returns the file that is being mapped */ + const File& getFile() const noexcept { return file; } + + /** Attempts to map the entire file into memory. */ + bool mapEntireFile(); + + /** Attempts to map a section of the file into memory. */ + bool mapSectionOfFile (const Range& samplesToMap); + + /** Returns the sample range that's currently memory-mapped and available for reading. */ + const Range& getMappedSection() const noexcept { return mappedSection; } + + /** Touches the memory for the given sample, to force it to be loaded into active memory. */ + void touchSample (int64 sample) const noexcept; + + /** Returns the number of bytes currently being mapped */ + size_t getNumBytesUsed() const { return map != nullptr ? map->getSize() : 0; } + +protected: + File file; + Range mappedSection; + ScopedPointer map; + int64 dataChunkStart, dataLength; + int bytesPerFrame; + + inline int64 sampleToFilePos (int64 sample) const noexcept { return dataChunkStart + sample * bytesPerFrame; } + inline int64 filePosToSample (int64 filePos) const noexcept { return (filePos - dataChunkStart) / bytesPerFrame; } + inline const void* sampleToPointer (int64 sample) const noexcept { return addBytesToPointer (map->getData(), sampleToFilePos (sample) - map->getRange().getStart()); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAudioFormatReader) +}; + + +#endif // __JUCE_MEMORYMAPPEDAUDIOFORMATREADER_JUCEHEADER__ diff --git a/modules/juce_audio_formats/juce_audio_formats.h b/modules/juce_audio_formats/juce_audio_formats.h index 24ceddc540..584316502a 100644 --- a/modules/juce_audio_formats/juce_audio_formats.h +++ b/modules/juce_audio_formats/juce_audio_formats.h @@ -105,6 +105,9 @@ namespace juce #ifndef __JUCE_AUDIOSUBSECTIONREADER_JUCEHEADER__ #include "format/juce_AudioSubsectionReader.h" #endif +#ifndef __JUCE_MEMORYMAPPEDAUDIOFORMATREADER_JUCEHEADER__ + #include "format/juce_MemoryMappedAudioFormatReader.h" +#endif #include "codecs/juce_AiffAudioFormat.h" #include "codecs/juce_CoreAudioFormat.h" #include "codecs/juce_FlacAudioFormat.h" diff --git a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp index 4e7cc8b42e..510c07a6e7 100644 --- a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp +++ b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp @@ -109,7 +109,7 @@ bool AudioThumbnailCache::loadThumb (AudioThumbnailBase& thumb, const int64 hash return true; } - return false; + return loadNewThumb (thumb, hashCode); } void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb, @@ -130,6 +130,8 @@ void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb, MemoryOutputStream out (te->data, false); thumb.saveTo (out); + + saveNewlyFinishedThumbnail (thumb, hashCode); } void AudioThumbnailCache::clear() @@ -168,3 +170,12 @@ void AudioThumbnailCache::writeToStream (OutputStream& out) for (int i = 0; i < thumbs.size(); ++i) thumbs.getUnchecked(i)->write (out); } + +void AudioThumbnailCache::saveNewlyFinishedThumbnail (const AudioThumbnailBase&, int64) +{ +} + +bool AudioThumbnailCache::loadNewThumb (AudioThumbnailBase&, int64) +{ + return false; +} diff --git a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h index c273446423..a78203f9e4 100644 --- a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h +++ b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.h @@ -51,7 +51,7 @@ public: explicit AudioThumbnailCache (int maxNumThumbsToStore); /** Destructor. */ - ~AudioThumbnailCache(); + virtual ~AudioThumbnailCache(); //============================================================================== /** Clears out any stored thumbnails. @@ -88,6 +88,12 @@ public: /** Returns the thread that client thumbnails can use. */ TimeSliceThread& getTimeSliceThread() noexcept { return thread; } +protected: + /** */ + virtual void saveNewlyFinishedThumbnail (const AudioThumbnailBase&, int64 hashCode); + /** */ + virtual bool loadNewThumb (AudioThumbnailBase&, int64 hashCode); + private: //============================================================================== TimeSliceThread thread;