| @@ -407,16 +407,8 @@ public: | |||||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
| int64 startSampleInFile, int numSamples) | 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) | if (numSamples <= 0) | ||||
| return true; | return true; | ||||
| @@ -440,27 +432,14 @@ public: | |||||
| jassert (! usesFloatingPointData); // (would need to add support for this if it's possible) | jassert (! usesFloatingPointData); // (would need to add support for this if it's possible) | ||||
| if (littleEndian) | 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 | 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; | startOffsetInDestBuffer += numThisTime; | ||||
| numSamples -= numThisTime; | numSamples -= numThisTime; | ||||
| @@ -469,6 +448,21 @@ public: | |||||
| return true; | 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; | int bytesPerFrame; | ||||
| int64 dataChunkStart; | int64 dataChunkStart; | ||||
| bool littleEndian; | bool littleEndian; | ||||
| @@ -661,6 +655,101 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter) | 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() | AiffAudioFormat::AiffAudioFormat() | ||||
| : AudioFormat (TRANS (aiffFormatName), StringArray (aiffExtensions)) | : AudioFormat (TRANS (aiffFormatName), StringArray (aiffExtensions)) | ||||
| @@ -711,6 +800,19 @@ AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, | |||||
| return nullptr; | 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, | AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, | ||||
| double sampleRate, | double sampleRate, | ||||
| unsigned int numberOfChannels, | unsigned int numberOfChannels, | ||||
| @@ -53,6 +53,8 @@ public: | |||||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | AudioFormatReader* createReaderFor (InputStream* sourceStream, | ||||
| bool deleteStreamIfOpeningFails); | bool deleteStreamIfOpeningFails); | ||||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&); | |||||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | ||||
| double sampleRateToUse, | double sampleRateToUse, | ||||
| unsigned int numberOfChannels, | unsigned int numberOfChannels, | ||||
| @@ -123,17 +123,8 @@ public: | |||||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
| int64 startSampleInFile, int numSamples) | 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) | if (numSamples <= 0) | ||||
| return true; | return true; | ||||
| @@ -712,17 +712,8 @@ public: | |||||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
| int64 startSampleInFile, int numSamples) | 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) | if (numSamples <= 0) | ||||
| return true; | return true; | ||||
| @@ -743,15 +734,9 @@ public: | |||||
| zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead)); | 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; | startOffsetInDestBuffer += numThisTime; | ||||
| numSamples -= numThisTime; | numSamples -= numThisTime; | ||||
| @@ -760,14 +745,27 @@ public: | |||||
| return true; | 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; | int64 dataChunkStart, dataLength; | ||||
| int bytesPerFrame; | |||||
| bool isRF64; | bool isRF64; | ||||
| private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader) | ||||
| }; | }; | ||||
| @@ -853,7 +851,6 @@ public: | |||||
| } | } | ||||
| private: | private: | ||||
| ScopedPointer<AudioData::Converter> converter; | |||||
| MemoryBlock tempBlock, bwavChunk, smplChunk, instChunk, cueChunk, listChunk; | MemoryBlock tempBlock, bwavChunk, smplChunk, instChunk, cueChunk, listChunk; | ||||
| uint64 lengthInSamples, bytesWritten; | uint64 lengthInSamples, bytesWritten; | ||||
| int64 headerPosition; | int64 headerPosition; | ||||
| @@ -1001,6 +998,82 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter) | 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() | WavAudioFormat::WavAudioFormat() | ||||
| : AudioFormat (TRANS (wavFormatName), StringArray (wavExtensions)) | : AudioFormat (TRANS (wavFormatName), StringArray (wavExtensions)) | ||||
| @@ -1040,6 +1113,19 @@ AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, | |||||
| return nullptr; | 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, | AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate, | ||||
| unsigned int numChannels, int bitsPerSample, | unsigned int numChannels, int bitsPerSample, | ||||
| const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | ||||
| @@ -120,6 +120,8 @@ public: | |||||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | AudioFormatReader* createReaderFor (InputStream* sourceStream, | ||||
| bool deleteStreamIfOpeningFails); | bool deleteStreamIfOpeningFails); | ||||
| MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file); | |||||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | ||||
| double sampleRateToUse, | double sampleRateToUse, | ||||
| unsigned int numberOfChannels, | unsigned int numberOfChannels, | ||||
| @@ -47,3 +47,8 @@ const String& AudioFormat::getFormatName() const { return formatN | |||||
| const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } | const StringArray& AudioFormat::getFileExtensions() const { return fileExtensions; } | ||||
| bool AudioFormat::isCompressed() { return false; } | bool AudioFormat::isCompressed() { return false; } | ||||
| StringArray AudioFormat::getQualityOptions() { return StringArray(); } | StringArray AudioFormat::getQualityOptions() { return StringArray(); } | ||||
| MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (const File&) | |||||
| { | |||||
| return nullptr; | |||||
| } | |||||
| @@ -28,7 +28,7 @@ | |||||
| #include "juce_AudioFormatReader.h" | #include "juce_AudioFormatReader.h" | ||||
| #include "juce_AudioFormatWriter.h" | #include "juce_AudioFormatWriter.h" | ||||
| #include "juce_MemoryMappedAudioFormatReader.h" | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -113,6 +113,13 @@ public: | |||||
| virtual AudioFormatReader* createReaderFor (InputStream* sourceStream, | virtual AudioFormatReader* createReaderFor (InputStream* sourceStream, | ||||
| bool deleteStreamIfOpeningFails) = 0; | 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. | /** 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 | 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), | : sampleRate (0), | ||||
| bitsPerSample (0), | bitsPerSample (0), | ||||
| lengthInSamples (0), | lengthInSamples (0), | ||||
| numChannels (0), | numChannels (0), | ||||
| usesFloatingPointData (false), | usesFloatingPointData (false), | ||||
| input (in), | input (in), | ||||
| formatName (formatName_) | |||||
| formatName (name) | |||||
| { | { | ||||
| } | } | ||||
| @@ -365,3 +364,56 @@ int64 AudioFormatReader::searchForLevel (int64 startSample, | |||||
| return -1; | 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 <DestSampleType, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DestType; | ||||
| typedef AudioData::Pointer <SourceSampleType, SourceEndianness, AudioData::Interleaved, AudioData::Const> SourceType; | 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) | 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; | dest += destOffset; | ||||
| if (i < numSourceChannels) | 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: | private: | ||||
| String formatName; | String formatName; | ||||
| @@ -51,17 +51,8 @@ AudioSubsectionReader::~AudioSubsectionReader() | |||||
| bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | ||||
| int64 startSampleInFile, int numSamples) | 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, | return source->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, | ||||
| startSampleInFile + startSample, numSamples); | 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__ | #ifndef __JUCE_AUDIOSUBSECTIONREADER_JUCEHEADER__ | ||||
| #include "format/juce_AudioSubsectionReader.h" | #include "format/juce_AudioSubsectionReader.h" | ||||
| #endif | #endif | ||||
| #ifndef __JUCE_MEMORYMAPPEDAUDIOFORMATREADER_JUCEHEADER__ | |||||
| #include "format/juce_MemoryMappedAudioFormatReader.h" | |||||
| #endif | |||||
| #include "codecs/juce_AiffAudioFormat.h" | #include "codecs/juce_AiffAudioFormat.h" | ||||
| #include "codecs/juce_CoreAudioFormat.h" | #include "codecs/juce_CoreAudioFormat.h" | ||||
| #include "codecs/juce_FlacAudioFormat.h" | #include "codecs/juce_FlacAudioFormat.h" | ||||
| @@ -109,7 +109,7 @@ bool AudioThumbnailCache::loadThumb (AudioThumbnailBase& thumb, const int64 hash | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | |||||
| return loadNewThumb (thumb, hashCode); | |||||
| } | } | ||||
| void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb, | void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb, | ||||
| @@ -130,6 +130,8 @@ void AudioThumbnailCache::storeThumb (const AudioThumbnailBase& thumb, | |||||
| MemoryOutputStream out (te->data, false); | MemoryOutputStream out (te->data, false); | ||||
| thumb.saveTo (out); | thumb.saveTo (out); | ||||
| saveNewlyFinishedThumbnail (thumb, hashCode); | |||||
| } | } | ||||
| void AudioThumbnailCache::clear() | void AudioThumbnailCache::clear() | ||||
| @@ -168,3 +170,12 @@ void AudioThumbnailCache::writeToStream (OutputStream& out) | |||||
| for (int i = 0; i < thumbs.size(); ++i) | for (int i = 0; i < thumbs.size(); ++i) | ||||
| thumbs.getUnchecked(i)->write (out); | 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); | explicit AudioThumbnailCache (int maxNumThumbsToStore); | ||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~AudioThumbnailCache(); | |||||
| virtual ~AudioThumbnailCache(); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Clears out any stored thumbnails. | /** Clears out any stored thumbnails. | ||||
| @@ -88,6 +88,12 @@ public: | |||||
| /** Returns the thread that client thumbnails can use. */ | /** Returns the thread that client thumbnails can use. */ | ||||
| TimeSliceThread& getTimeSliceThread() noexcept { return thread; } | TimeSliceThread& getTimeSliceThread() noexcept { return thread; } | ||||
| protected: | |||||
| /** */ | |||||
| virtual void saveNewlyFinishedThumbnail (const AudioThumbnailBase&, int64 hashCode); | |||||
| /** */ | |||||
| virtual bool loadNewThumb (AudioThumbnailBase&, int64 hashCode); | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| TimeSliceThread thread; | TimeSliceThread thread; | ||||