11namespace tracktion {
inline namespace engine
17 :
juce::AudioFormatReader (nullptr,
"FallbackReader")
24static void clearSetOfChannels (
int*
const* channels,
int numChannels,
int offset,
int numSamples)
noexcept
26 for (
int i = 0; i < numChannels; ++i)
27 if (
auto chan = (
float*) channels[i])
28 juce::FloatVectorOperations::clear (chan + offset, numSamples);
46 auto desired = expected + durationMs;
53 desired = expected + durationMs;
68 : cache (c), file (f), info (f.getInfo())
71 if (info.lengthInSamples <= cache.cacheSizeSamples)
76 enum { readAheadSamples = 48000 };
86 for (
auto r : clients)
88 const auto readPos = r->readPos.load();
89 const auto loopLength = r->loopLength.load();
91 if (r->getReferenceCount() > 1 && readPos > -readAheadSamples)
94 if (readPos + readAheadSamples > r->loopStart + loopLength)
104 for (
auto pos : readPoints)
105 touchAllReaders ({ pos, pos + 128 });
107 for (
auto pos : readPoints)
108 touchAllReaders ({ pos + 128, pos + 4096 });
110 for (
int distanceAhead = 4096; distanceAhead < 48000; distanceAhead += 8192)
111 for (
auto pos : readPoints)
112 touchAllReaders ({ pos + distanceAhead, pos + distanceAhead + 8192 });
117 for (
auto* r : readers)
121 auto section = r->getMappedSection();
135 if (mapEntireFile && readers.
size() > 0)
152 if (
auto r = createNewReader (
nullptr))
158 failedToOpenFile =
true;
166 bool anythingChanged =
false;
167 bool needToPurgeUnusedClients =
false;
168 auto blockSize = cache.cacheSizeSamples;
169 auto lastPossibleBlockIndex = (
int) ((info.lengthInSamples - 1) / blockSize);
175 for (
auto r : clients)
177 if (r->getReferenceCount() <= 1)
179 needToPurgeUnusedClients =
true;
183 const auto readPos = r->readPos.load();
184 const auto loopStart = r->loopStart.load();
185 const auto loopLength = r->loopLength.load();
189 auto loopEnd = loopStart + loopLength;
190 auto start =
std::max (0, (
int) (
std::max (loopStart, readPos - 256) / blockSize));
191 auto end =
std::min (lastPossibleBlockIndex, (
int) (
std::min (loopEnd, readPos + readAheadSamples) / blockSize));
193 for (
int i = start; i <= end; ++i)
196 if (readPos + readAheadSamples > loopEnd)
201 auto start =
std::max (0, (
int) ((readPos - 256) / blockSize));
202 auto end =
std::min (lastPossibleBlockIndex, (
int) ((readPos + readAheadSamples) / blockSize));
204 for (
int i = start; i <= end; ++i)
211 if (blocksNeeded != currentBlocks)
218 for (
int i = 0; i < blocksNeeded.
size(); ++i)
221 const int existingIndex = currentBlocks.
indexOf (block);
225 if (existingIndex >= 0)
228 readers.
set (existingIndex,
nullptr,
false);
232 auto pos = block * (SampleCount) blockSize;
234 newReader = createNewReader (&range);
237 if (newReader !=
nullptr)
238 newReaders.
add (newReader);
240 blocksNeeded.
remove (i--);
247 currentBlocks.
swapWith (blocksNeeded);
252 for (
auto m : newReaders)
254 totalBytesInUse -=
static_cast<int64_t> (m->getNumBytesUsed());
256 anythingChanged =
true;
259 if (needToPurgeUnusedClients)
263 for (
int i = clients.size(); --i >= 0;)
264 if (clients.getObjectPointerUnchecked(i)->getReferenceCount() <= 1)
268 return anythingChanged;
273 for (
int i = 0; i < currentBlocks.
size(); ++i)
274 DBG (
"File " << file.getFile().
getFileName() <<
" " << currentBlocks[i]
275 <<
" " << readers[i]->getMappedSection().getStart()
276 <<
" - " << readers[i]->getMappedSection().getEnd());
286 : r->mapEntireFile())
287 && ! r->getMappedSection().isEmpty())
289 totalBytesInUse +=
static_cast<int64_t> (r->getNumBytesUsed());
290 failedToOpenFile =
false;
299 void purgeOrphanReaders()
303 for (
int i = clients.size(); --i >= 0;)
304 if (clients.getObjectPointerUnchecked (i)->getReferenceCount() <= 1)
308 bool isUnused()
const
312 return clients.isEmpty();
319 currentBlocks.
clear();
325 failedToOpenFile =
false;
338 reader = f.findReaderFor (startSample);
340 if (reader !=
nullptr)
364 const int elapsed = (
int) (now - startTime);
366 if (elapsed > timeoutMs)
370 juce::Thread::yield();
384 bool isLocked =
true;
389 bool read (SampleCount startSample,
int*
const* destSamples,
int numDestChannels,
390 int startOffsetInDestBuffer,
int numSamples,
int timeoutMs)
392 jassert (destSamples !=
nullptr);
394 jassert (startOffsetInDestBuffer >= 0);
396 bool allDataRead =
true;
398 while (numSamples > 0)
400 if (startSample >= info.lengthInSamples)
402 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
406 const LockedReaderFinder l (*
this, startSample, timeoutMs);
407 SCOPED_REALTIME_CHECK
409 if (l.isLocked && l.reader !=
nullptr)
411 auto numThisTime =
int (
std::min<int64_t> (numSamples, l.reader->getMappedSection().getEnd() - startSample));
413 l.reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startSample, numThisTime);
415 startSample += numThisTime;
416 startOffsetInDestBuffer += numThisTime;
417 numSamples -= numThisTime;
422 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
423 DBG (
"*** Cache miss");
432 bool getRange (SampleCount startSample,
int numSamples,
433 float& lmax,
float& lmin,
float& rmax,
float& rmin,
437 bool allDataRead =
true, isFirst =
true;
439 while (numSamples > 0)
441 const LockedReaderFinder l (*
this, startSample, timeoutMs);
443 if (l.isLocked && l.reader !=
nullptr)
445 auto numThisTime =
std::min (numSamples, (
int) (l.reader->getMappedSection().getEnd() - startSample));
450 l.reader->readMaxLevels (startSample, numThisTime, lmin, lmax, rmin, rmax);
454 float lmin2, lmax2, rmin2, rmax2;
455 l.reader->readMaxLevels (startSample, numThisTime, lmin2, lmax2, rmin2, rmax2);
463 startSample += numThisTime;
464 numSamples -= numThisTime;
471 lmin = lmax = rmin = rmax = 0;
481 void addClient (Reader* r)
487 AudioFileCache& cache;
501 bool mapEntireFile =
false;
510 for (
auto r : readers)
511 if (r != nullptr && r->getMappedSection().contains (
sample))
542 if (owner.serviceNextReader())
547 if (now > lastOldFlePurge + 2000)
549 lastOldFlePurge = now;
550 owner.purgeOldFiles();
582 owner.touchReaders();
592AudioFileCache::AudioFileCache (
Engine& e) : engine (e)
595 const int defaultSize = 6 * 48000;
598 setCacheSizeSamples (
static_cast<juce::int64> (engine.getPropertyStorage().getProperty (SettingID::cacheSizeSamples, defaultSize)));
601AudioFileCache::~AudioFileCache()
605 purgeOrphanReaders();
606 jassert (activeFiles.isEmpty());
611void AudioFileCache::stopThreads()
615 if (mapperThread !=
nullptr)
616 mapperThread->signalThreadShouldExit();
618 if (refresherThread !=
nullptr)
619 refresherThread->signalThreadShouldExit();
621 mapperThread.reset();
622 refresherThread.reset();
625void AudioFileCache::setCacheSizeSamples (SampleCount samples)
628 samples =
juce::jlimit ((SampleCount) 48000, (SampleCount) 48000 * 60, samples);
630 if (cacheSizeSamples != samples)
634 cacheSizeSamples = samples;
635 engine.
getPropertyStorage().setProperty (SettingID::cacheSizeSamples, (
int) cacheSizeSamples);
640 purgeOrphanReaders();
645 mapperThread->startThread (juce::Thread::Priority::normal);
648 refresherThread->startThread (juce::Thread::Priority::high);
653AudioFileCache::CachedFile* AudioFileCache::getOrCreateCachedFile (
const AudioFile& f)
655 for (
auto s : activeFiles)
656 if (s->info.hashCode == f.getHash())
661 for (
auto af : manager)
663 if (af->canHandleFile (f.getFile()))
665 auto fs =
new CachedFile (*
this, f);
666 activeFiles.add (fs);
674void AudioFileCache::releaseFile (
const AudioFile& file)
678 for (
auto f : activeFiles)
683void AudioFileCache::releaseAllFiles()
688 for (
auto f : activeFiles)
692void AudioFileCache::validateFile (
const AudioFile& file)
696 for (
auto f : activeFiles)
701void AudioFileCache::purgeOldFiles()
708 for (
auto f : activeFiles)
709 f->purgeOrphanReaders();
711 for (
int i = activeFiles.size(); --i >= 0;)
713 auto f = activeFiles.getUnchecked (i);
715 if (f->lastReadTime < oldestAllowedTime && f->isUnused())
716 activeFiles.remove (i);
720bool AudioFileCache::serviceNextReader()
724 for (
int i = activeFiles.size(); --i >= 0;)
726 if (++nextFileToService >= activeFiles.size())
727 nextFileToService = 0;
729 auto* f = activeFiles.getUnchecked (nextFileToService);
731 if (f->updateBlocks())
738void AudioFileCache::touchReaders()
744 for (
auto f : activeFiles)
747 totalBytes += f->totalBytesInUse;
750 totalBytesUsed = totalBytes;
753bool AudioFileCache::hasCacheMissed (
bool clearMissedFlag)
755 const bool didMiss = cacheMissed;
768void AudioFileCache::nextBlockStarted()
772 lastBlockDurationMs.
store (current > last ? current
773 : last + 0.2 * (current - last),
774 std::memory_order_release);
777bool AudioFileCache::hasMappedReader (
const AudioFile& af, SampleCount c)
const
781 for (
auto s : activeFiles)
782 if (s->info.hashCode == af.getHash())
783 if (CachedFile::LockedReaderFinder (*s, c, 0).reader)
795 if (
auto f = getOrCreateCachedFile (file))
797 auto r =
new Reader (*
this, f,
nullptr);
802 if (
auto reader = AudioFileUtils::createReaderFor (engine, file.getFile()))
804 backgroundReaderThread.
startThread (juce::Thread::Priority::low);
816 int samplesToBuffer)>& createFallbackReader)
821 if (
auto f = getOrCreateCachedFile (file))
823 auto r =
new Reader (*
this, f,
nullptr);
828 if (
auto reader = AudioFileUtils::createReaderFor (engine, file.getFile()))
830 backgroundReaderThread.
startThread (juce::Thread::Priority::low);
831 auto fallbackReader = createFallbackReader (reader, backgroundReaderThread,
833 return new Reader (*
this,
nullptr, std::move (fallbackReader));
840 int samplesToBuffer)>&
841 createFallbackReader)
843 backgroundReaderThread.
startThread (juce::Thread::Priority::low);
844 auto fallbackReader = createFallbackReader (backgroundReaderThread,
846 return new Reader (*
this,
nullptr, std::move (fallbackReader));
849void AudioFileCache::purgeOrphanReaders()
851 for (CachedFile* f : activeFiles)
852 f->purgeOrphanReaders();
854 for (
int i = activeFiles.size(); --i >= 0;)
855 if (activeFiles.getUnchecked(i)->isUnused())
856 activeFiles.remove (i);
861 : cache (c), file (f), fallbackReader (
std::
move (fallback))
863 jassert (file !=
nullptr || fallbackReader !=
nullptr);
866AudioFileCache::Reader::~Reader()
870void AudioFileCache::Reader::setReadPosition (SampleCount pos)
noexcept
872 const auto localLoopStart = loopStart.load();
873 const auto localLoopLength = loopLength.load();
878 readPos = localLoopStart + (pos % localLoopLength);
883int AudioFileCache::Reader::getNumChannels() const noexcept
885 return file !=
nullptr ?
static_cast<CachedFile*
> (file)->info.numChannels
886 : (
int) fallbackReader->numChannels;
889double AudioFileCache::Reader::getSampleRate() const noexcept
891 return file !=
nullptr ?
static_cast<CachedFile*
> (file)->info.sampleRate
892 : (
int) fallbackReader->sampleRate;
895void AudioFileCache::Reader::setLoopRange (SampleRange newRange)
897 loopStart = newRange.getStart();
898 loopLength = newRange.getLength();
901bool AudioFileCache::Reader::readSamples (
int numSamples,
904 int startOffsetInDestBuffer,
908 jassert (numSamples < CachedFile::readAheadSamples);
912 if (cache.engine.getEngineBehaviour().isDescriptionOfWaveDevicesSupported())
914 static constexpr int maxNumChannels = 32;
915 float* chans[maxNumChannels] = {};
916 auto numSourceChans =
std::min (maxNumChannels, sourceBufferChannels.
size());
917 int highestUsedSourceChan = 0;
919 for (
int destIndex = 0; destIndex < numDestChans; ++destIndex)
922 auto destData = destBuffer.
getWritePointer (destIndex, startOffsetInDestBuffer);
925 if (sourceIndex >= 0 && sourceIndex < maxNumChannels)
927 chans[sourceIndex] = destData;
928 highestUsedSourceChan =
std::max (highestUsedSourceChan, sourceIndex);
932 juce::FloatVectorOperations::clear (destData, numSamples);
936 if (readSamples ((
int**) chans, numSourceChans, 0, numSamples, timeoutMs))
938 bool isFloatingPoint = (file !=
nullptr) ?
static_cast<CachedFile*
> (file)->info.isFloatingPoint
939 : fallbackReader->usesFloatingPointData;
941 if (! isFloatingPoint)
942 for (
int i = 0; i <= highestUsedSourceChan; ++i)
943 if (
auto chan = chans[i])
944 juce::FloatVectorOperations::convertFixedToFloat (chan, (
const int*) chan, 1.0f / 0x7fffffff, numSamples);
951 float* chans[2] = {};
952 bool dupeChannel =
false;
954 if (numDestChans > 1)
961 if (getNumChannels() > 1)
985 if (readSamples ((
int**) chans, 2, 0, numSamples, timeoutMs))
987 const bool isFloatingPoint = (file !=
nullptr) ?
static_cast<CachedFile*
> (file)->info.isFloatingPoint
988 : fallbackReader->usesFloatingPointData;
990 if (! isFloatingPoint)
991 for (
int i = 0; i < 2; ++i)
992 if (
auto* chan = chans[i])
993 juce::FloatVectorOperations::convertFixedToFloat (chan, (
const int*) chan, 1.0f / 0x7fffffff, numSamples);
997 if (chans[0] ==
nullptr)
998 juce::FloatVectorOperations::copy (destBuffer.
getWritePointer (0), chans[1], numSamples);
999 else if (chans[1] ==
nullptr)
1000 juce::FloatVectorOperations::copy (destBuffer.
getWritePointer (1), chans[0], numSamples);
1010bool AudioFileCache::Reader::readSamples (
int*
const* destSamples,
int numDestChannels,
1011 int startOffsetInDestBuffer,
int numSamples,
int timeoutMs)
1013 jassert (numSamples < CachedFile::readAheadSamples);
1014 jassert (getReferenceCount() > 1 || file ==
nullptr);
1019 auto silence = (
int)
std::min (-readPos, (SampleCount) numSamples);
1021 for (
int i = numDestChannels; --i >= 0;)
1022 if (destSamples[i] !=
nullptr)
1023 juce::FloatVectorOperations::clear ((
float*) destSamples[i], silence);
1025 startOffsetInDestBuffer += silence;
1026 numSamples -= silence;
1029 if (numSamples <= 0)
1034 const ScopedFileRead sfr (cache);
1036 if (loopLength == 0)
1038 if (
auto cf =
static_cast<CachedFile*
> (file))
1040 allOk = cf->read (readPos, destSamples, numDestChannels, startOffsetInDestBuffer, numSamples, timeoutMs);
1044 fallbackReader->setReadTimeout (timeoutMs);
1045 allOk = fallbackReader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, readPos, numSamples);
1048 readPos += numSamples;
1050 else if (loopLength > 1)
1052 while (numSamples > 0)
1056 auto numToRead = (
int)
std::min ((SampleCount) numSamples, loopStart + loopLength - readPos);
1058 if (
auto cf =
static_cast<CachedFile*
> (file))
1060 allOk = cf->read (readPos, destSamples, numDestChannels, startOffsetInDestBuffer, numToRead, timeoutMs) && allOk;
1064 fallbackReader->setReadTimeout (timeoutMs);
1065 allOk = fallbackReader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, readPos, numToRead) && allOk;
1068 readPos += numToRead;
1070 if (readPos >= loopStart + loopLength)
1071 readPos -= loopLength;
1073 startOffsetInDestBuffer += numToRead;
1074 numSamples -= numToRead;
1081 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
1085 cache.cacheMissed =
true;
1090bool AudioFileCache::Reader::getRange (
int numSamples,
float& lmax,
float& lmin,
float& rmax,
float& rmin,
int timeoutMs)
1092 jassert (getReferenceCount() > 1 || file ==
nullptr);
1096 if (
auto cf =
static_cast<CachedFile*
> (file))
1098 ok = cf->getRange (readPos, numSamples, lmax, lmin, rmax, rmin, timeoutMs);
1102 fallbackReader->setReadTimeout (timeoutMs);
1103 fallbackReader->readMaxLevels (readPos, numSamples, lmin, lmax, rmin, rmax);
1107 readPos += numSamples;
1117 auto info = file.getInfo();
1119 sampleRate = info.sampleRate;
1120 bitsPerSample = (
unsigned int) info.bitsPerSample;
1121 lengthInSamples = info.lengthInSamples;
1122 numChannels = (
unsigned int) info.numChannels;
1123 usesFloatingPointData = info.isFloatingPoint;
1124 metadataValues = info.metadata;
1126 reader = file.engine->getAudioFileManager().cache.createReader (file);
1132 float& lowestLeft,
float& highestLeft,
1133 float& lowestRight,
float& highestRight)
override
1135 reader->setReadPosition (startSample);
1136 reader->getRange ((
int) numSamples, highestLeft, lowestLeft, highestRight, lowestRight, -1);
1139 bool readSamples (
int*
const* destSamples,
int numDestChannels,
1140 int startOffsetInDestBuffer,
juce::int64 startSampleInFile,
1141 int numSamples)
override
1143 reader->setReadPosition (startSampleInFile);
1144 return reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples, -1);
void swapWith(OtherArrayType &otherArray) noexcept
ElementType getUnchecked(int index) const
void ensureStorageAllocated(int minNumElements)
int size() const noexcept
void remove(int indexToRemove)
int indexOf(ParameterType elementToLookFor) const
bool addIfNotAlreadyThere(ParameterType newElement)
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
int size() const noexcept
ChannelType getTypeOfChannel(int channelIndex) const noexcept
int getChannelIndexForType(ChannelType type) const noexcept
String getFileName() const
static void JUCE_CALLTYPE disableDenormalisedNumberSupport(bool shouldDisable=true) noexcept
int size() const noexcept
ObjectClass * getUnchecked(int index) const noexcept
ObjectClass * set(int indexToChange, ObjectClass *newObject, bool deleteOldElement=true)
bool isEmpty() const noexcept
void swapWith(OtherArrayType &otherArray) noexcept
void clear(bool deleteObjects=true)
ObjectClass * add(ObjectClass *newObject)
constexpr ValueType getStart() const noexcept
constexpr Range getIntersectionWith(Range other) const noexcept
constexpr ValueType getEnd() const noexcept
bool tryEnterRead() const noexcept
void exitRead() const noexcept
bool wait(double timeOutMilliseconds) const
bool threadShouldExit() const
bool stopThread(int timeOutMilliseconds)
static uint32 getApproximateMillisecondCounter() noexcept
static double getMillisecondCounterHiRes() noexcept
static uint32 getMillisecondCounter() noexcept
TimeDuration getCpuUsage() const
Returns the amount of time spent reading files in the last block.
Reader::Ptr createReader(const AudioFile &)
Creates a Reader to read an AudioFile.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
static int getNumPlayingTransports(Engine &)
Returns the number of Edits currently playing.
T compare_exchange_weak(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
IntegerType negativeAwareModulo(IntegerType dividend, const IntegerType divisor) noexcept
Represents a duration in real-life time.
FallbackReader()
Constructor.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.