| @@ -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<AudioData::Int32, AudioData::Int8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 32: ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData, | |||
| destSamples, startOffsetInDestBuffer, numDestChannels, | |||
| tempBuffer, (int) numChannels, numThisTime); | |||
| else | |||
| { | |||
| switch (bitsPerSample) | |||
| { | |||
| case 8: ReadHelper<AudioData::Int32, AudioData::Int8, AudioData::BigEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::BigEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::BigEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 32: ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::BigEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData, | |||
| destSamples, startOffsetInDestBuffer, numDestChannels, | |||
| tempBuffer, (int) numChannels, numThisTime); | |||
| startOffsetInDestBuffer += numThisTime; | |||
| numSamples -= numThisTime; | |||
| @@ -469,6 +448,21 @@ public: | |||
| return true; | |||
| } | |||
| template <typename Endianness> | |||
| 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<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 32: ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::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<int64> (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<AudioData::LittleEndian> | |||
| (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer, | |||
| numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples); | |||
| else | |||
| AiffAudioFormatReader::copySampleData<AudioData::BigEndian> | |||
| (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<int64> (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<AudioData::UInt8> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, min0, max0, min1, max1); | |||
| else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| private: | |||
| const bool littleEndian; | |||
| template <typename SampleType> | |||
| void scanMinAndMax (int64 startSampleInFile, int64 numSamples, | |||
| float& min0, float& max0, float& min1, float& max1) const noexcept | |||
| { | |||
| if (littleEndian) | |||
| scanMinAndMax2<SampleType, AudioData::LittleEndian> (startSampleInFile, numSamples, min0, max0, min1, max1); | |||
| else | |||
| scanMinAndMax2<SampleType, AudioData::BigEndian> (startSampleInFile, numSamples, min0, max0, min1, max1); | |||
| } | |||
| template <typename SampleType, typename Endianness> | |||
| void scanMinAndMax2 (int64 startSampleInFile, int64 numSamples, | |||
| float& min0, float& max0, float& min1, float& max1) const noexcept | |||
| { | |||
| typedef AudioData::Pointer <SampleType, Endianness, AudioData::Interleaved, AudioData::Const> 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, | |||
| @@ -53,6 +53,8 @@ public: | |||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||
| bool deleteStreamIfOpeningFails); | |||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&); | |||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||
| double sampleRateToUse, | |||
| unsigned int numberOfChannels, | |||
| @@ -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; | |||
| @@ -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<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); break; | |||
| case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, tempBuffer, (int) numChannels, numThisTime); | |||
| else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::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<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); | |||
| else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| private: | |||
| ScopedPointer<AudioData::Converter> 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<AudioData::Converter> 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<int64> (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<int64> (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<AudioData::UInt8> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, min0, max0, min1, max1); | |||
| else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, min0, max0, min1, max1); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| private: | |||
| template <typename SampleType> | |||
| void scanMinAndMax (int64 startSampleInFile, int64 numSamples, | |||
| float& min0, float& max0, float& min1, float& max1) const | |||
| { | |||
| typedef AudioData::Pointer <SampleType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> 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*/) | |||
| @@ -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, | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| @@ -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<int64> (0, lengthInSamples)); | |||
| } | |||
| bool MemoryMappedAudioFormatReader::mapSectionOfFile (const Range<int64>& samplesToMap) | |||
| { | |||
| if (map == nullptr || samplesToMap != mappedSection) | |||
| { | |||
| map = nullptr; | |||
| const Range<int64> fileRange (sampleToFilePos (samplesToMap.getStart()), | |||
| sampleToFilePos (samplesToMap.getEnd())); | |||
| map = new MemoryMappedFile (file, fileRange, MemoryMappedFile::readOnly); | |||
| if (map->getData() == nullptr) | |||
| map = nullptr; | |||
| else | |||
| mappedSection = Range<int64> (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. | |||
| } | |||
| } | |||
| @@ -237,13 +237,15 @@ protected: | |||
| typedef AudioData::Pointer <DestSampleType, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DestType; | |||
| typedef AudioData::Pointer <SourceSampleType, SourceEndianness, AudioData::Interleaved, AudioData::Const> SourceType; | |||
| static void read (int** destData, int destOffset, int numDestChannels, const void* sourceData, int numSourceChannels, int numSamples) noexcept | |||
| template <typename TargetType> | |||
| 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; | |||
| @@ -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); | |||
| @@ -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<int64>& samplesToMap); | |||
| /** Returns the sample range that's currently memory-mapped and available for reading. */ | |||
| const Range<int64>& 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<int64> mappedSection; | |||
| ScopedPointer<MemoryMappedFile> 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__ | |||
| @@ -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" | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||