mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Reservoir: Factor out reservoir management code
This commit is contained in:
parent
abd5fe4a69
commit
930a3299f2
7 changed files with 347 additions and 217 deletions
|
|
@ -167,6 +167,8 @@ namespace FlacNamespace
|
|||
//==============================================================================
|
||||
static const char* const flacFormatName = "FLAC file";
|
||||
|
||||
template <typename Item>
|
||||
auto emptyRange (Item item) { return Range<Item>::emptyRange (item); }
|
||||
|
||||
//==============================================================================
|
||||
class FlacReader : public AudioFormatReader
|
||||
|
|
@ -217,66 +219,61 @@ public:
|
|||
reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true);
|
||||
}
|
||||
|
||||
// returns the number of samples read
|
||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples) override
|
||||
{
|
||||
if (! ok)
|
||||
return false;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
if (startSampleInFile >= reservoirStart
|
||||
&& startSampleInFile < reservoirStart + samplesInReservoir)
|
||||
{
|
||||
auto num = (int) jmin ((int64) numSamples,
|
||||
reservoirStart + samplesInReservoir - startSampleInFile);
|
||||
const auto getBufferedRange = [this] { return bufferedRange; };
|
||||
|
||||
jassert (num > 0);
|
||||
const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead)
|
||||
{
|
||||
const auto bufferIndices = rangeToRead - bufferedRange.getStart();
|
||||
const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile);
|
||||
|
||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
|
||||
{
|
||||
if (destSamples[i] != nullptr)
|
||||
memcpy (destSamples[i] + startOffsetInDestBuffer,
|
||||
reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)),
|
||||
(size_t) num * sizeof (int));
|
||||
{
|
||||
memcpy (destSamples[i] + writePos,
|
||||
reservoir.getReadPointer (i) + bufferIndices.getStart(),
|
||||
(size_t) bufferIndices.getLength() * sizeof (int));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
startOffsetInDestBuffer += num;
|
||||
startSampleInFile += num;
|
||||
numSamples -= num;
|
||||
}
|
||||
else
|
||||
const auto fillReservoir = [this] (const int64 requestedStart)
|
||||
{
|
||||
if (startSampleInFile >= lengthInSamples)
|
||||
if (requestedStart >= lengthInSamples)
|
||||
{
|
||||
samplesInReservoir = 0;
|
||||
bufferedRange = emptyRange (requestedStart);
|
||||
return;
|
||||
}
|
||||
else if (startSampleInFile < reservoirStart
|
||||
|| startSampleInFile > reservoirStart + jmax (samplesInReservoir, (int64) 511))
|
||||
|
||||
if (requestedStart < bufferedRange.getStart()
|
||||
|| jmax (bufferedRange.getEnd(), bufferedRange.getStart() + (int64) 511) < requestedStart)
|
||||
{
|
||||
// had some problems with flac crashing if the read pos is aligned more
|
||||
// accurately than this. Probably fixed in newer versions of the library, though.
|
||||
reservoirStart = (int) (startSampleInFile & ~511);
|
||||
samplesInReservoir = 0;
|
||||
FLAC__stream_decoder_seek_absolute (decoder, (FlacNamespace::FLAC__uint64) reservoirStart);
|
||||
bufferedRange = emptyRange (requestedStart & ~511);
|
||||
FLAC__stream_decoder_seek_absolute (decoder, (FlacNamespace::FLAC__uint64) bufferedRange.getStart());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
reservoirStart += samplesInReservoir;
|
||||
samplesInReservoir = 0;
|
||||
|
||||
bufferedRange = emptyRange (bufferedRange.getEnd());
|
||||
FLAC__stream_decoder_process_single (decoder);
|
||||
}
|
||||
};
|
||||
|
||||
if (samplesInReservoir == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples },
|
||||
getBufferedRange,
|
||||
readFromReservoir,
|
||||
fillReservoir);
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
if (! remainingSamples.isEmpty())
|
||||
for (int i = numDestChannels; --i >= 0;)
|
||||
if (destSamples[i] != nullptr)
|
||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) numSamples * sizeof (int));
|
||||
}
|
||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) remainingSamples.getLength() * sizeof (int));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -304,14 +301,14 @@ public:
|
|||
|
||||
if (src != nullptr)
|
||||
{
|
||||
auto* dest = reinterpret_cast<int*> (reservoir.getWritePointer(i));
|
||||
auto* dest = reinterpret_cast<int*> (reservoir.getWritePointer (i));
|
||||
|
||||
for (int j = 0; j < numSamples; ++j)
|
||||
dest[j] = src[j] << bitsToShift;
|
||||
}
|
||||
}
|
||||
|
||||
samplesInReservoir = numSamples;
|
||||
bufferedRange.setLength (numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +365,7 @@ public:
|
|||
private:
|
||||
FlacNamespace::FLAC__StreamDecoder* decoder;
|
||||
AudioBuffer<float> reservoir;
|
||||
int64 reservoirStart = 0, samplesInReservoir = 0;
|
||||
Range<int64> bufferedRange;
|
||||
bool ok = false, scanningForLength = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader)
|
||||
|
|
|
|||
|
|
@ -158,43 +158,31 @@ public:
|
|||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples) override
|
||||
{
|
||||
while (numSamples > 0)
|
||||
{
|
||||
auto numAvailable = (reservoirStart + samplesInReservoir - startSampleInFile);
|
||||
const auto getBufferedRange = [this] { return bufferedRange; };
|
||||
|
||||
if (startSampleInFile >= reservoirStart && numAvailable > 0)
|
||||
const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead)
|
||||
{
|
||||
// got a few samples overlapping, so use them before seeking..
|
||||
|
||||
auto numToUse = jmin ((int64) numSamples, numAvailable);
|
||||
const auto bufferIndices = rangeToRead - bufferedRange.getStart();
|
||||
const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile);
|
||||
|
||||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
|
||||
if (destSamples[i] != nullptr)
|
||||
memcpy (destSamples[i] + startOffsetInDestBuffer,
|
||||
reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)),
|
||||
(size_t) numToUse * sizeof (float));
|
||||
memcpy (destSamples[i] + writePos,
|
||||
reservoir.getReadPointer (i) + bufferIndices.getStart(),
|
||||
(size_t) bufferIndices.getLength() * sizeof (float));
|
||||
};
|
||||
|
||||
startSampleInFile += numToUse;
|
||||
numSamples -= (int) numToUse;
|
||||
startOffsetInDestBuffer += (int) numToUse;
|
||||
|
||||
if (numSamples == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (startSampleInFile < reservoirStart
|
||||
|| startSampleInFile + numSamples > reservoirStart + samplesInReservoir)
|
||||
const auto fillReservoir = [this] (int64 requestedStart)
|
||||
{
|
||||
// buffer miss, so refill the reservoir
|
||||
reservoirStart = jmax (0, (int) startSampleInFile);
|
||||
samplesInReservoir = reservoir.getNumSamples();
|
||||
const auto newStart = jmax ((int64) 0, requestedStart);
|
||||
bufferedRange = Range<int64> { newStart, newStart + reservoir.getNumSamples() };
|
||||
|
||||
if (reservoirStart != (int) ov_pcm_tell (&ovFile))
|
||||
ov_pcm_seek (&ovFile, reservoirStart);
|
||||
if (bufferedRange.getStart() != ov_pcm_tell (&ovFile))
|
||||
ov_pcm_seek (&ovFile, bufferedRange.getStart());
|
||||
|
||||
int bitStream = 0;
|
||||
int offset = 0;
|
||||
int numToRead = (int) samplesInReservoir;
|
||||
int numToRead = (int) bufferedRange.getLength();
|
||||
|
||||
while (numToRead > 0)
|
||||
{
|
||||
|
|
@ -215,15 +203,17 @@ public:
|
|||
|
||||
if (numToRead > 0)
|
||||
reservoir.clear (offset, numToRead);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples },
|
||||
getBufferedRange,
|
||||
readFromReservoir,
|
||||
fillReservoir);
|
||||
|
||||
if (! remainingSamples.isEmpty())
|
||||
for (int i = numDestChannels; --i >= 0;)
|
||||
if (destSamples[i] != nullptr)
|
||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) numSamples * sizeof (int));
|
||||
}
|
||||
zeromem (destSamples[i] + startOffsetInDestBuffer, (size_t) remainingSamples.getLength() * sizeof (int));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -261,7 +251,7 @@ private:
|
|||
OggVorbisNamespace::OggVorbis_File ovFile;
|
||||
OggVorbisNamespace::ov_callbacks callbacks;
|
||||
AudioBuffer<float> reservoir;
|
||||
int64 reservoirStart = 0, samplesInReservoir = 0;
|
||||
Range<int64> bufferedRange;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
|
|||
#include "containers/juce_PropertySet.h"
|
||||
#include "memory/juce_SharedResourcePointer.h"
|
||||
#include "memory/juce_AllocationHooks.h"
|
||||
#include "memory/juce_Reservoir.h"
|
||||
|
||||
#if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS)
|
||||
#include "native/juce_mac_ObjCHelpers.h"
|
||||
|
|
|
|||
100
modules/juce_core/memory/juce_Reservoir.h
Normal file
100
modules/juce_core/memory/juce_Reservoir.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Helper functions for managing buffered readers.
|
||||
*/
|
||||
struct Reservoir
|
||||
{
|
||||
/** Attempts to read the requested range from some kind of input stream,
|
||||
with intermediate buffering in a 'reservoir'.
|
||||
|
||||
While there are still samples in the requested range left to read, this
|
||||
function will check whether the next part of the requested range is
|
||||
already loaded into the reservoir. If the range is available, then
|
||||
doBufferedRead will call readFromReservoir with the range that should
|
||||
be copied to the output buffer. If the range is not available,
|
||||
doBufferedRead will call fillReservoir to request that a new region is
|
||||
loaded into the reservoir. It will repeat these steps until either the
|
||||
entire requested region has been read, or the stream ends.
|
||||
|
||||
This will return the range that could not be read successfully, if any.
|
||||
An empty range implies that the entire read was successful.
|
||||
|
||||
Note that all ranges, including those provided as arguments to the
|
||||
callbacks, are relative to the original unbuffered input. That is, if
|
||||
getBufferedRange returns the range [200, 300), then readFromReservoir
|
||||
might be passed the range [250, 300) in order to copy the final 50
|
||||
samples from the reservoir.
|
||||
|
||||
@param rangeToRead the absolute position of the range that should
|
||||
be read
|
||||
@param getBufferedRange a function void -> Range<Index> that returns
|
||||
the region currently held in the reservoir
|
||||
@param readFromReservoir a function Range<Index> -> void that can be
|
||||
used to copy samples from the region in the
|
||||
reservoir specified in the input range
|
||||
@param fillReservoir a function Index -> void that is given a
|
||||
requested read location, and that should
|
||||
attempt to fill the reservoir starting at this
|
||||
location. After this function,
|
||||
getBufferedRange should return the new region
|
||||
contained in the managed buffer
|
||||
*/
|
||||
template <typename Index, typename GetBufferedRange, typename ReadFromReservoir, typename FillReservoir>
|
||||
static Range<Index> doBufferedRead (Range<Index> rangeToRead,
|
||||
GetBufferedRange&& getBufferedRange,
|
||||
ReadFromReservoir&& readFromReservoir,
|
||||
FillReservoir&& fillReservoir)
|
||||
{
|
||||
while (! rangeToRead.isEmpty())
|
||||
{
|
||||
const auto rangeToReadInBuffer = rangeToRead.getIntersectionWith (getBufferedRange());
|
||||
|
||||
if (rangeToReadInBuffer.isEmpty())
|
||||
{
|
||||
fillReservoir (rangeToRead.getStart());
|
||||
|
||||
const auto newRange = getBufferedRange();
|
||||
|
||||
if (newRange.isEmpty() || ! newRange.contains (rangeToRead.getStart()))
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
readFromReservoir (rangeToReadInBuffer);
|
||||
|
||||
rangeToRead.setStart (rangeToReadInBuffer.getEnd());
|
||||
}
|
||||
}
|
||||
|
||||
return rangeToRead;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -40,11 +40,11 @@ static int calcBufferStreamBufferSize (int requestedSize, InputStream* source) n
|
|||
//==============================================================================
|
||||
BufferedInputStream::BufferedInputStream (InputStream* sourceStream, int size, bool takeOwnership)
|
||||
: source (sourceStream, takeOwnership),
|
||||
bufferSize (calcBufferStreamBufferSize (size, sourceStream)),
|
||||
position (sourceStream->getPosition()),
|
||||
bufferStart (position)
|
||||
bufferedRange (sourceStream->getPosition(), sourceStream->getPosition()),
|
||||
position (bufferedRange.getStart()),
|
||||
bufferLength (calcBufferStreamBufferSize (size, sourceStream))
|
||||
{
|
||||
buffer.malloc (bufferSize);
|
||||
buffer.malloc (bufferLength);
|
||||
}
|
||||
|
||||
BufferedInputStream::BufferedInputStream (InputStream& sourceStream, int size)
|
||||
|
|
@ -52,9 +52,7 @@ BufferedInputStream::BufferedInputStream (InputStream& sourceStream, int size)
|
|||
{
|
||||
}
|
||||
|
||||
BufferedInputStream::~BufferedInputStream()
|
||||
{
|
||||
}
|
||||
BufferedInputStream::~BufferedInputStream() = default;
|
||||
|
||||
//==============================================================================
|
||||
char BufferedInputStream::peekByte()
|
||||
|
|
@ -62,7 +60,7 @@ char BufferedInputStream::peekByte()
|
|||
if (! ensureBuffered())
|
||||
return 0;
|
||||
|
||||
return position < lastReadPos ? buffer[(int) (position - bufferStart)] : 0;
|
||||
return position < lastReadPos ? buffer[(int) (position - bufferedRange.getStart())] : 0;
|
||||
}
|
||||
|
||||
int64 BufferedInputStream::getTotalLength()
|
||||
|
|
@ -90,20 +88,19 @@ bool BufferedInputStream::ensureBuffered()
|
|||
{
|
||||
auto bufferEndOverlap = lastReadPos - bufferOverlap;
|
||||
|
||||
if (position < bufferStart || position >= bufferEndOverlap)
|
||||
if (position < bufferedRange.getStart() || position >= bufferEndOverlap)
|
||||
{
|
||||
int bytesRead;
|
||||
int bytesRead = 0;
|
||||
|
||||
if (position < lastReadPos
|
||||
&& position >= bufferEndOverlap
|
||||
&& position >= bufferStart)
|
||||
&& position >= bufferedRange.getStart())
|
||||
{
|
||||
auto bytesToKeep = (int) (lastReadPos - position);
|
||||
memmove (buffer, buffer + (int) (position - bufferStart), (size_t) bytesToKeep);
|
||||
memmove (buffer, buffer + (int) (position - bufferedRange.getStart()), (size_t) bytesToKeep);
|
||||
|
||||
bufferStart = position;
|
||||
bytesRead = source->read (buffer + bytesToKeep,
|
||||
(int) (bufferSize - bytesToKeep));
|
||||
(int) (bufferLength - bytesToKeep));
|
||||
|
||||
if (bytesRead < 0)
|
||||
return false;
|
||||
|
|
@ -113,75 +110,62 @@ bool BufferedInputStream::ensureBuffered()
|
|||
}
|
||||
else
|
||||
{
|
||||
bufferStart = position;
|
||||
|
||||
if (! source->setPosition (bufferStart))
|
||||
if (! source->setPosition (position))
|
||||
return false;
|
||||
|
||||
bytesRead = source->read (buffer, bufferSize);
|
||||
bytesRead = (int) source->read (buffer, (size_t) bufferLength);
|
||||
|
||||
if (bytesRead < 0)
|
||||
return false;
|
||||
|
||||
lastReadPos = bufferStart + bytesRead;
|
||||
lastReadPos = position + bytesRead;
|
||||
}
|
||||
|
||||
while (bytesRead < bufferSize)
|
||||
bufferedRange = Range<int64> (position, lastReadPos);
|
||||
|
||||
while (bytesRead < bufferLength)
|
||||
buffer[bytesRead++] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int BufferedInputStream::read (void* destBuffer, int maxBytesToRead)
|
||||
int BufferedInputStream::read (void* destBuffer, const int maxBytesToRead)
|
||||
{
|
||||
jassert (destBuffer != nullptr && maxBytesToRead >= 0);
|
||||
const auto initialPosition = position;
|
||||
|
||||
if (position >= bufferStart
|
||||
&& position + maxBytesToRead <= lastReadPos)
|
||||
const auto getBufferedRange = [this] { return bufferedRange; };
|
||||
|
||||
const auto readFromReservoir = [this, &destBuffer, &initialPosition] (const Range<int64> rangeToRead)
|
||||
{
|
||||
memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) maxBytesToRead);
|
||||
position += maxBytesToRead;
|
||||
return maxBytesToRead;
|
||||
}
|
||||
memcpy (static_cast<char*> (destBuffer) + (rangeToRead.getStart() - initialPosition),
|
||||
buffer + (rangeToRead.getStart() - bufferedRange.getStart()),
|
||||
(size_t) rangeToRead.getLength());
|
||||
};
|
||||
|
||||
if (position < bufferStart || position >= lastReadPos)
|
||||
if (! ensureBuffered())
|
||||
return 0;
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
while (maxBytesToRead > 0)
|
||||
const auto fillReservoir = [this] (int64 requestedStart)
|
||||
{
|
||||
auto numToRead = jmin (maxBytesToRead, (int) (lastReadPos - position));
|
||||
position = requestedStart;
|
||||
ensureBuffered();
|
||||
};
|
||||
|
||||
if (numToRead > 0)
|
||||
{
|
||||
memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) numToRead);
|
||||
maxBytesToRead -= numToRead;
|
||||
bytesRead += numToRead;
|
||||
position += numToRead;
|
||||
destBuffer = static_cast<char*> (destBuffer) + numToRead;
|
||||
}
|
||||
const auto remaining = Reservoir::doBufferedRead (Range<int64> (position, position + maxBytesToRead),
|
||||
getBufferedRange,
|
||||
readFromReservoir,
|
||||
fillReservoir);
|
||||
|
||||
auto oldLastReadPos = lastReadPos;
|
||||
|
||||
if (! ensureBuffered()
|
||||
|| oldLastReadPos == lastReadPos
|
||||
|| isExhausted())
|
||||
break;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
const auto bytesRead = maxBytesToRead - remaining.getLength();
|
||||
position = remaining.getStart();
|
||||
return (int) bytesRead;
|
||||
}
|
||||
|
||||
String BufferedInputStream::readString()
|
||||
{
|
||||
if (position >= bufferStart
|
||||
if (position >= bufferedRange.getStart()
|
||||
&& position < lastReadPos)
|
||||
{
|
||||
auto maxChars = (int) (lastReadPos - position);
|
||||
auto* src = buffer + (int) (position - bufferStart);
|
||||
auto* src = buffer + (int) (position - bufferedRange.getStart());
|
||||
|
||||
for (int i = 0; i < maxChars; ++i)
|
||||
{
|
||||
|
|
@ -203,16 +187,67 @@ String BufferedInputStream::readString()
|
|||
|
||||
struct BufferedInputStreamTests : public UnitTest
|
||||
{
|
||||
template <typename Fn, size_t... Ix, typename Values>
|
||||
static void applyImpl (Fn&& fn, std::index_sequence<Ix...>, Values&& values)
|
||||
{
|
||||
fn (std::get<Ix> (values)...);
|
||||
}
|
||||
|
||||
template <typename Fn, typename... Values>
|
||||
static void apply (Fn&& fn, std::tuple<Values...> values)
|
||||
{
|
||||
applyImpl (fn, std::make_index_sequence<sizeof... (Values)>(), values);
|
||||
}
|
||||
|
||||
template <typename Fn, typename Values>
|
||||
static void allCombinationsImpl (Fn&& fn, Values&& values)
|
||||
{
|
||||
apply (fn, values);
|
||||
}
|
||||
|
||||
template <typename Fn, typename Values, typename Range, typename... Ranges>
|
||||
static void allCombinationsImpl (Fn&& fn, Values&& values, Range&& range, Ranges&&... ranges)
|
||||
{
|
||||
for (auto& item : range)
|
||||
allCombinationsImpl (fn, std::tuple_cat (values, std::tie (item)), ranges...);
|
||||
}
|
||||
|
||||
template <typename Fn, typename... Ranges>
|
||||
static void allCombinations (Fn&& fn, Ranges&&... ranges)
|
||||
{
|
||||
allCombinationsImpl (fn, std::tie(), ranges...);
|
||||
}
|
||||
|
||||
BufferedInputStreamTests()
|
||||
: UnitTest ("BufferedInputStream", UnitTestCategories::streams)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
const MemoryBlock data ("abcdefghijklmnopqrstuvwxyz", 26);
|
||||
const MemoryBlock testBufferA ("abcdefghijklmnopqrstuvwxyz", 26);
|
||||
|
||||
const auto testBufferB = [&]
|
||||
{
|
||||
MemoryBlock mb { 8192 };
|
||||
auto r = getRandom();
|
||||
|
||||
std::for_each (mb.begin(), mb.end(), [&] (char& item)
|
||||
{
|
||||
item = (char) r.nextInt (std::numeric_limits<char>::max());
|
||||
});
|
||||
|
||||
return mb;
|
||||
}();
|
||||
|
||||
const MemoryBlock buffers[] { testBufferA, testBufferB };
|
||||
const int readSizes[] { 3, 10, 50 };
|
||||
const bool shouldPeek[] { false, true };
|
||||
|
||||
const auto runTest = [this] (const MemoryBlock& data, const int readSize, const bool peek)
|
||||
{
|
||||
MemoryInputStream mi (data, true);
|
||||
|
||||
BufferedInputStream stream (mi, (int) data.getSize());
|
||||
BufferedInputStream stream (mi, jmin (200, (int) data.getSize()));
|
||||
|
||||
beginTest ("Read");
|
||||
|
||||
|
|
@ -226,10 +261,16 @@ struct BufferedInputStreamTests : public UnitTest
|
|||
|
||||
while (numBytesRead < data.getSize())
|
||||
{
|
||||
if (peek)
|
||||
expectEquals (stream.peekByte(), *(char*) (data.begin() + numBytesRead));
|
||||
|
||||
numBytesRead += (size_t) stream.read (&readBuffer[numBytesRead], 3);
|
||||
const auto startingPos = numBytesRead;
|
||||
numBytesRead += (size_t) stream.read (readBuffer.begin() + numBytesRead, readSize);
|
||||
|
||||
expect (std::equal (readBuffer.begin() + startingPos,
|
||||
readBuffer.begin() + numBytesRead,
|
||||
data.begin() + startingPos,
|
||||
data.begin() + numBytesRead));
|
||||
expectEquals (stream.getPosition(), (int64) numBytesRead);
|
||||
expectEquals (stream.getNumBytesRemaining(), (int64) (data.getSize() - numBytesRead));
|
||||
expect (stream.isExhausted() == (numBytesRead == data.getSize()));
|
||||
|
|
@ -268,6 +309,9 @@ struct BufferedInputStreamTests : public UnitTest
|
|||
expectEquals (stream.getPosition(), (int64) data.getSize());
|
||||
expectEquals (stream.getNumBytesRemaining(), (int64) 0);
|
||||
expect (stream.isExhausted());
|
||||
};
|
||||
|
||||
allCombinations (runTest, buffers, readSizes, shouldPeek);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ public:
|
|||
private:
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<InputStream> source;
|
||||
int bufferSize;
|
||||
int64 position, lastReadPos = 0, bufferStart, bufferOverlap = 128;
|
||||
Range<int64> bufferedRange;
|
||||
int64 position, bufferLength, lastReadPos = 0, bufferOverlap = 128;
|
||||
HeapBlock<char> buffer;
|
||||
bool ensureBuffered();
|
||||
|
||||
|
|
|
|||
|
|
@ -52,9 +52,7 @@ MemoryInputStream::MemoryInputStream (MemoryBlock&& source)
|
|||
dataSize = internalCopy.getSize();
|
||||
}
|
||||
|
||||
MemoryInputStream::~MemoryInputStream()
|
||||
{
|
||||
}
|
||||
MemoryInputStream::~MemoryInputStream() = default;
|
||||
|
||||
int64 MemoryInputStream::getTotalLength()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue