tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AudioFileCache.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14//==============================================================================
15//==============================================================================
17 : juce::AudioFormatReader (nullptr, "FallbackReader")
18{
19}
20
21
22//==============================================================================
23//==============================================================================
24static void clearSetOfChannels (int* const* channels, int numChannels, int offset, int numSamples) noexcept
25{
26 for (int i = 0; i < numChannels; ++i)
27 if (auto chan = (float*) channels[i])
28 juce::FloatVectorOperations::clear (chan + offset, numSamples);
29}
30
31//==============================================================================
32//==============================================================================
34{
36 : cache (c)
37 {
38 }
39
41 {
42 const auto durationMs = juce::Time::getMillisecondCounterHiRes() - startTimeMs;
43
44 // We have to roll our own fetch_add until it's available on all platforms for atomic<double>
45 auto expected = cache.blockDurationMs.load (std::memory_order_acquire);
46 auto desired = expected + durationMs;
47
48 while (! cache.blockDurationMs.compare_exchange_weak (expected, desired,
51 {
52 expected = cache.blockDurationMs.load (std::memory_order_acquire);
53 desired = expected + durationMs;
54 }
55 }
56
57 AudioFileCache& cache;
58 const double startTimeMs { juce::Time::getMillisecondCounterHiRes() };
59};
60
61
62//==============================================================================
63//==============================================================================
65{
66public:
68 : cache (c), file (f), info (f.getInfo())
69 {
70 #if ! JUCE_64BIT
71 if (info.lengthInSamples <= cache.cacheSizeSamples)
72 #endif
73 mapEntireFile = true;
74 }
75
76 enum { readAheadSamples = 48000 };
77
78 void touchFiles()
79 {
80 juce::Array<SampleCount> readPoints;
81 readPoints.ensureStorageAllocated (64);
82
83 {
84 const juce::ScopedReadLock sl (clientListLock);
85
86 for (auto r : clients)
87 {
88 const auto readPos = r->readPos.load();
89 const auto loopLength = r->loopLength.load();
90
91 if (r->getReferenceCount() > 1 && readPos > -readAheadSamples)
92 {
93 if (loopLength > 0)
94 if (readPos + readAheadSamples > r->loopStart + loopLength)
95 readPoints.addIfNotAlreadyThere (r->loopStart);
96
97 readPoints.addIfNotAlreadyThere (std::max (SampleCount(), readPos));
98 }
99 }
100 }
101
102 const juce::ScopedReadLock sl (readerLock);
103
104 for (auto pos : readPoints)
105 touchAllReaders ({ pos, pos + 128 });
106
107 for (auto pos : readPoints)
108 touchAllReaders ({ pos + 128, pos + 4096 });
109
110 for (int distanceAhead = 4096; distanceAhead < 48000; distanceAhead += 8192)
111 for (auto pos : readPoints)
112 touchAllReaders ({ pos + distanceAhead, pos + distanceAhead + 8192 });
113 }
114
115 void touchAllReaders (SampleRange range) const
116 {
117 for (auto* r : readers)
118 {
119 if (r != nullptr)
120 {
121 auto section = r->getMappedSection();
122 range = range.getIntersectionWith (SampleRange (section.getStart(), section.getEnd()));
123
124 for (auto i = range.getStart(); i < range.getEnd(); i += 64)
125 r->touchSample (i);
126 }
127 }
128 }
129
130 bool updateBlocks()
131 {
132 {
133 const juce::ScopedReadLock sl (readerLock);
134
135 if (mapEntireFile && readers.size() > 0)
136 return false;
137 }
138
139 const juce::ScopedLock scl (blockUpdateLock);
140
141 if (mapEntireFile)
142 {
143 if (readers.isEmpty())
144 {
145 if (failedToOpenFile
147 < lastFailedOpenAttempt + 4000 + (uint32_t) random.nextInt (3000))
148 return false;
149
150 const juce::ScopedWriteLock sl (readerLock);
151
152 if (auto r = createNewReader (nullptr))
153 {
154 readers.add (r);
155 }
156 else
157 {
158 failedToOpenFile = true;
159 lastFailedOpenAttempt = juce::Time::getMillisecondCounter();
160 }
161 }
162
163 return false;
164 }
165
166 bool anythingChanged = false;
167 bool needToPurgeUnusedClients = false;
168 auto blockSize = cache.cacheSizeSamples;
169 auto lastPossibleBlockIndex = (int) ((info.lengthInSamples - 1) / blockSize);
170 juce::Array<int> blocksNeeded;
171
172 {
173 const juce::ScopedReadLock sl (clientListLock);
174
175 for (auto r : clients)
176 {
177 if (r->getReferenceCount() <= 1)
178 {
179 needToPurgeUnusedClients = true;
180 }
181 else
182 {
183 const auto readPos = r->readPos.load();
184 const auto loopStart = r->loopStart.load();
185 const auto loopLength = r->loopLength.load();
186
187 if (loopLength > 0)
188 {
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));
192
193 for (int i = start; i <= end; ++i)
194 blocksNeeded.addIfNotAlreadyThere (i);
195
196 if (readPos + readAheadSamples > loopEnd)
197 blocksNeeded.addIfNotAlreadyThere ((int) (loopStart / blockSize));
198 }
199 else
200 {
201 auto start = std::max (0, (int) ((readPos - 256) / blockSize));
202 auto end = std::min (lastPossibleBlockIndex, (int) ((readPos + readAheadSamples) / blockSize));
203
204 for (int i = start; i <= end; ++i)
205 blocksNeeded.addIfNotAlreadyThere (i);
206 }
207 }
208 }
209 }
210
211 if (blocksNeeded != currentBlocks)
212 {
214
215 {
216 const juce::ScopedReadLock sl (readerLock);
217
218 for (int i = 0; i < blocksNeeded.size(); ++i)
219 {
220 const int block = blocksNeeded.getUnchecked(i);
221 const int existingIndex = currentBlocks.indexOf (block);
222
224
225 if (existingIndex >= 0)
226 {
227 newReader = readers.getUnchecked (existingIndex);
228 readers.set (existingIndex, nullptr, false);
229 }
230 else
231 {
232 auto pos = block * (SampleCount) blockSize;
233 SampleRange range (pos, pos + blockSize);
234 newReader = createNewReader (&range);
235 }
236
237 if (newReader != nullptr)
238 newReaders.add (newReader);
239 else
240 blocksNeeded.remove (i--);
241 }
242 }
243
244 {
245 const juce::ScopedWriteLock sl (readerLock);
246 newReaders.swapWith (readers);
247 currentBlocks.swapWith (blocksNeeded);
248
249 jassert (readers.size() == currentBlocks.size());
250 }
251
252 for (auto m : newReaders)
253 if (m != nullptr)
254 totalBytesInUse -= static_cast<int64_t> (m->getNumBytesUsed());
255
256 anythingChanged = true;
257 }
258
259 if (needToPurgeUnusedClients)
260 {
261 const juce::ScopedWriteLock sl (clientListLock);
262
263 for (int i = clients.size(); --i >= 0;)
264 if (clients.getObjectPointerUnchecked(i)->getReferenceCount() <= 1)
265 clients.remove (i);
266 }
267
268 return anythingChanged;
269 }
270
271 void dumpBlocks()
272 {
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());
277 }
278
279 juce::MemoryMappedAudioFormatReader* createNewReader (const SampleRange* range)
280 {
282 std::unique_ptr<juce::MemoryMappedAudioFormatReader> r (AudioFileUtils::createMemoryMappedReader (cache.engine, file.getFile(), af));
283
284 if (r != nullptr
285 && (range != nullptr ? r->mapSectionOfFile (juce::Range<juce::int64> (range->getStart(), range->getEnd()))
286 : r->mapEntireFile())
287 && ! r->getMappedSection().isEmpty())
288 {
289 totalBytesInUse += static_cast<int64_t> (r->getNumBytesUsed());
290 failedToOpenFile = false;
291
292 info = AudioFileInfo (file, r.get(), af);
293 return r.release();
294 }
295
296 return {};
297 }
298
299 void purgeOrphanReaders()
300 {
301 const juce::ScopedWriteLock sl (clientListLock);
302
303 for (int i = clients.size(); --i >= 0;)
304 if (clients.getObjectPointerUnchecked (i)->getReferenceCount() <= 1)
305 clients.remove (i);
306 }
307
308 bool isUnused() const
309 {
310 const juce::ScopedReadLock sl (clientListLock);
311
312 return clients.isEmpty();
313 }
314
315 void releaseReader()
316 {
317 const juce::ScopedWriteLock sl (readerLock);
318 readers.clear();
319 currentBlocks.clear();
320 }
321
322 void validateFile()
323 {
324 const juce::ScopedLock scl (blockUpdateLock);
325 failedToOpenFile = false;
326 }
327
329 {
330 LockedReaderFinder (CachedFile& f, SampleCount startSample, int timeoutMs) : lock (f.readerLock)
331 {
332 uint32_t startTime = 0;
333
334 for (;;)
335 {
336 if (lock.tryEnterRead())
337 {
338 reader = f.findReaderFor (startSample);
339
340 if (reader != nullptr)
341 return;
342
343 lock.exitRead();
344 }
345
346 if (timeoutMs < 0)
347 {
348 if (startTime != 0) // second failed after calling updateBlocks failed
349 break;
350
351 f.updateBlocks();
352 startTime = 1;
353 continue;
354 }
355
356 if (timeoutMs == 0)
357 break;
358
360
361 if (startTime == 0)
362 startTime = now;
363
364 const int elapsed = (int) (now - startTime);
365
366 if (elapsed > timeoutMs)
367 break;
368
369 if (elapsed > 0)
370 juce::Thread::yield();
371 }
372
373 isLocked = false;
374 }
375
377 {
378 if (isLocked)
379 lock.exitRead();
380 }
381
382 juce::MemoryMappedAudioFormatReader* reader = nullptr;
384 bool isLocked = true;
385
387 };
388
389 bool read (SampleCount startSample, int* const* destSamples, int numDestChannels,
390 int startOffsetInDestBuffer, int numSamples, int timeoutMs)
391 {
392 jassert (destSamples != nullptr);
393 jassert (startSample >= 0);
394 jassert (startOffsetInDestBuffer >= 0);
395
396 bool allDataRead = true;
397
398 while (numSamples > 0)
399 {
400 if (startSample >= info.lengthInSamples)
401 {
402 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
403 break;
404 }
405
406 const LockedReaderFinder l (*this, startSample, timeoutMs);
407 SCOPED_REALTIME_CHECK
408
409 if (l.isLocked && l.reader != nullptr)
410 {
411 auto numThisTime = int (std::min<int64_t> (numSamples, l.reader->getMappedSection().getEnd() - startSample));
412
413 l.reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startSample, numThisTime);
414
415 startSample += numThisTime;
416 startOffsetInDestBuffer += numThisTime;
417 numSamples -= numThisTime;
418 }
419 else
420 {
421 allDataRead = false;
422 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
423 DBG ("*** Cache miss");
424 break;
425 }
426 }
427
429 return allDataRead;
430 }
431
432 bool getRange (SampleCount startSample, int numSamples,
433 float& lmax, float& lmin, float& rmax, float& rmin,
434 const int timeoutMs)
435 {
436 jassert (startSample >= 0);
437 bool allDataRead = true, isFirst = true;
438
439 while (numSamples > 0)
440 {
441 const LockedReaderFinder l (*this, startSample, timeoutMs);
442
443 if (l.isLocked && l.reader != nullptr)
444 {
445 auto numThisTime = std::min (numSamples, (int) (l.reader->getMappedSection().getEnd() - startSample));
446
447 if (isFirst)
448 {
449 isFirst = false;
450 l.reader->readMaxLevels (startSample, numThisTime, lmin, lmax, rmin, rmax);
451 }
452 else
453 {
454 float lmin2, lmax2, rmin2, rmax2;
455 l.reader->readMaxLevels (startSample, numThisTime, lmin2, lmax2, rmin2, rmax2);
456
457 lmin = std::min (lmin, lmin2);
458 lmax = std::max (lmax, lmax2);
459 rmin = std::min (rmin, rmin2);
460 rmax = std::max (rmax, rmax2);
461 }
462
463 startSample += numThisTime;
464 numSamples -= numThisTime;
465 }
466 else
467 {
468 allDataRead = false;
469
470 if (isFirst)
471 lmin = lmax = rmin = rmax = 0;
472
473 break;
474 }
475 }
476
478 return allDataRead;
479 }
480
481 void addClient (Reader* r)
482 {
483 juce::ScopedWriteLock sl (clientListLock);
484 clients.add (r);
485 }
486
487 AudioFileCache& cache;
488 AudioFile file;
489 AudioFileInfo info;
490
492 std::atomic<int64_t> totalBytesInUse { 0 };
493
494private:
497
498 juce::CriticalSection blockUpdateLock;
499 juce::Array<int> currentBlocks;
500
501 bool mapEntireFile = false;
502 std::atomic<bool> failedToOpenFile { false };
503 uint32_t lastFailedOpenAttempt = 0;
504 juce::Random random;
505
506 juce::ReadWriteLock clientListLock, readerLock;
507
508 juce::MemoryMappedAudioFormatReader* findReaderFor (SampleCount sample) const
509 {
510 for (auto r : readers)
511 if (r != nullptr && r->getMappedSection().contains (sample))
512 return r;
513
514 return {};
515 }
516
518};
519
520//==============================================================================
522{
523public:
525 : juce::Thread ("CacheMapper"), owner (c)
526 {
527 }
528
530 {
531 stopThread (15000);
532 }
533
534 void run()
535 {
537
538 uint32_t lastOldFlePurge = 0;
539
540 while (! threadShouldExit())
541 {
542 if (owner.serviceNextReader())
543 continue;
544
546
547 if (now > lastOldFlePurge + 2000)
548 {
549 lastOldFlePurge = now;
550 owner.purgeOldFiles();
551 continue;
552 }
553
554 wait (20);
555
556 //DBG ("Total cache mapping: " << owner.getBytesInUse() / (1024 * 1024) << " Mb");
557 }
558 }
559
560 AudioFileCache& owner;
561};
562
563//==============================================================================
565{
566public:
567 RefresherThread (AudioFileCache& c) : juce::Thread ("CacheRefresher"), owner (c)
568 {
569 }
570
572 {
573 stopThread (15000);
574 }
575
576 void run()
577 {
579
580 while (! threadShouldExit())
581 {
582 owner.touchReaders();
583
584 wait (TransportControl::getNumPlayingTransports (owner.engine) > 0 ? 10 : 250);
585 }
586 }
587
588 AudioFileCache& owner;
589};
590
591//==============================================================================
592AudioFileCache::AudioFileCache (Engine& e) : engine (e)
593{
595 const int defaultSize = 6 * 48000;
596
597 // TODO: when we drop 32-bit support, delete the cache size and related code
598 setCacheSizeSamples (static_cast<juce::int64> (engine.getPropertyStorage().getProperty (SettingID::cacheSizeSamples, defaultSize)));
599}
600
601AudioFileCache::~AudioFileCache()
602{
604 stopThreads();
605 purgeOrphanReaders();
606 jassert (activeFiles.isEmpty());
607 activeFiles.clear();
608}
609
610//==============================================================================
611void AudioFileCache::stopThreads()
612{
614
615 if (mapperThread != nullptr)
616 mapperThread->signalThreadShouldExit();
617
618 if (refresherThread != nullptr)
619 refresherThread->signalThreadShouldExit();
620
621 mapperThread.reset();
622 refresherThread.reset();
623}
624
625void AudioFileCache::setCacheSizeSamples (SampleCount samples)
626{
628 samples = juce::jlimit ((SampleCount) 48000, (SampleCount) 48000 * 60, samples);
629
630 if (cacheSizeSamples != samples)
631 {
632 stopThreads();
633
634 cacheSizeSamples = samples;
635 engine.getPropertyStorage().setProperty (SettingID::cacheSizeSamples, (int) cacheSizeSamples);
636
637 {
638 const juce::ScopedWriteLock sl (fileListLock);
639
640 purgeOrphanReaders();
641 releaseAllFiles();
642 }
643
644 mapperThread = std::make_unique<MapperThread> (*this);
645 mapperThread->startThread (juce::Thread::Priority::normal);
646
647 refresherThread = std::make_unique<RefresherThread> (*this);
648 refresherThread->startThread (juce::Thread::Priority::high);
649 }
650}
651
652//==============================================================================
653AudioFileCache::CachedFile* AudioFileCache::getOrCreateCachedFile (const AudioFile& f)
654{
655 for (auto s : activeFiles)
656 if (s->info.hashCode == f.getHash())
657 return s;
658
659 auto& manager = engine.getAudioFileFormatManager().memoryMappedFormatManager;
660
661 for (auto af : manager)
662 {
663 if (af->canHandleFile (f.getFile()))
664 {
665 auto fs = new CachedFile (*this, f);
666 activeFiles.add (fs);
667 return fs;
668 }
669 }
670
671 return {};
672}
673
674void AudioFileCache::releaseFile (const AudioFile& file)
675{
676 const juce::ScopedReadLock sl (fileListLock);
677
678 for (auto f : activeFiles)
679 if (f->file == file)
680 f->releaseReader();
681}
682
683void AudioFileCache::releaseAllFiles()
684{
686 const juce::ScopedReadLock sl (fileListLock);
687
688 for (auto f : activeFiles)
689 f->releaseReader();
690}
691
692void AudioFileCache::validateFile (const AudioFile& file)
693{
694 const juce::ScopedReadLock sl (fileListLock);
695
696 for (auto f : activeFiles)
697 if (f->file == file)
698 f->validateFile();
699}
700
701void AudioFileCache::purgeOldFiles()
702{
704 auto oldestAllowedTime = juce::Time::getApproximateMillisecondCounter() - 2000;
705
706 const juce::ScopedWriteLock sl (fileListLock);
707
708 for (auto f : activeFiles)
709 f->purgeOrphanReaders();
710
711 for (int i = activeFiles.size(); --i >= 0;)
712 {
713 auto f = activeFiles.getUnchecked (i);
714
715 if (f->lastReadTime < oldestAllowedTime && f->isUnused())
716 activeFiles.remove (i);
717 }
718}
719
720bool AudioFileCache::serviceNextReader()
721{
722 const juce::ScopedReadLock sl (fileListLock);
723
724 for (int i = activeFiles.size(); --i >= 0;)
725 {
726 if (++nextFileToService >= activeFiles.size())
727 nextFileToService = 0;
728
729 auto* f = activeFiles.getUnchecked (nextFileToService);
730
731 if (f->updateBlocks())
732 return true;
733 }
734
735 return false;
736}
737
738void AudioFileCache::touchReaders()
739{
740 int64_t totalBytes = 0;
741
742 const juce::ScopedReadLock sl (fileListLock);
743
744 for (auto f : activeFiles)
745 {
746 f->touchFiles();
747 totalBytes += f->totalBytesInUse;
748 }
749
750 totalBytesUsed = totalBytes;
751}
752
753bool AudioFileCache::hasCacheMissed (bool clearMissedFlag)
754{
755 const bool didMiss = cacheMissed;
756
757 if (clearMissedFlag)
758 cacheMissed = false;
759
760 return didMiss;
761}
762
764{
765 return TimeDuration::fromSeconds (lastBlockDurationMs.load (std::memory_order_acquire) / 1000.0);
766}
767
768void AudioFileCache::nextBlockStarted()
769{
770 const auto last = lastBlockDurationMs.load (std::memory_order_acquire);
771 const auto current = blockDurationMs.exchange (0.0, std::memory_order_acq_rel);
772 lastBlockDurationMs.store (current > last ? current
773 : last + 0.2 * (current - last),
774 std::memory_order_release);
775}
776
777bool AudioFileCache::hasMappedReader (const AudioFile& af, SampleCount c) const
778{
779 const juce::ScopedReadLock rl (fileListLock);
780
781 for (auto s : activeFiles)
782 if (s->info.hashCode == af.getHash())
783 if (CachedFile::LockedReaderFinder (*s, c, 0).reader)
784 return true;
785
786 return false;
787}
788
789//==============================================================================
791{
793 const juce::ScopedWriteLock sl (fileListLock);
794
795 if (auto f = getOrCreateCachedFile (file))
796 {
797 auto r = new Reader (*this, f, nullptr);
798 f->addClient (r);
799 return r;
800 }
801
802 if (auto reader = AudioFileUtils::createReaderFor (engine, file.getFile()))
803 {
804 backgroundReaderThread.startThread (juce::Thread::Priority::low);
805
806 return new Reader (*this, nullptr, std::make_unique<BufferingAudioReaderWrapper> (std::make_unique<juce::BufferingAudioReader> (reader, backgroundReaderThread,
807 48000 * 5)));
808 }
809
810 return {};
811}
812
815 juce::TimeSliceThread& timeSliceThread,
816 int samplesToBuffer)>& createFallbackReader)
817{
819 const juce::ScopedWriteLock sl (fileListLock);
820
821 if (auto f = getOrCreateCachedFile (file))
822 {
823 auto r = new Reader (*this, f, nullptr);
824 f->addClient (r);
825 return r;
826 }
827
828 if (auto reader = AudioFileUtils::createReaderFor (engine, file.getFile()))
829 {
830 backgroundReaderThread.startThread (juce::Thread::Priority::low);
831 auto fallbackReader = createFallbackReader (reader, backgroundReaderThread,
832 48000 * 5);
833 return new Reader (*this, nullptr, std::move (fallbackReader));
834 }
835
836 return {};
837}
838
839AudioFileCache::Reader::Ptr AudioFileCache::createFallbackReader (const std::function<std::unique_ptr<FallbackReader> (juce::TimeSliceThread& timeSliceThread,
840 int samplesToBuffer)>&
841 createFallbackReader)
842{
843 backgroundReaderThread.startThread (juce::Thread::Priority::low);
844 auto fallbackReader = createFallbackReader (backgroundReaderThread,
845 48000 * 5);
846 return new Reader (*this, nullptr, std::move (fallbackReader));
847}
848
849void AudioFileCache::purgeOrphanReaders()
850{
851 for (CachedFile* f : activeFiles)
852 f->purgeOrphanReaders();
853
854 for (int i = activeFiles.size(); --i >= 0;)
855 if (activeFiles.getUnchecked(i)->isUnused())
856 activeFiles.remove (i);
857}
858
859//==============================================================================
860AudioFileCache::Reader::Reader (AudioFileCache& c, void* f, std::unique_ptr<FallbackReader> fallback)
861 : cache (c), file (f), fallbackReader (std::move (fallback))
862{
863 jassert (file != nullptr || fallbackReader != nullptr);
864}
865
866AudioFileCache::Reader::~Reader()
867{
868}
869
870void AudioFileCache::Reader::setReadPosition (SampleCount pos) noexcept
871{
872 const auto localLoopStart = loopStart.load();
873 const auto localLoopLength = loopLength.load();
874
875 if (loopLength == 0)
876 readPos = pos;
877 else if (pos >= 0)
878 readPos = localLoopStart + (pos % localLoopLength);
879 else
880 readPos = localLoopStart + juce::negativeAwareModulo (pos, localLoopLength);
881}
882
883int AudioFileCache::Reader::getNumChannels() const noexcept
884{
885 return file != nullptr ? static_cast<CachedFile*> (file)->info.numChannels
886 : (int) fallbackReader->numChannels;
887}
888
889double AudioFileCache::Reader::getSampleRate() const noexcept
890{
891 return file != nullptr ? static_cast<CachedFile*> (file)->info.sampleRate
892 : (int) fallbackReader->sampleRate;
893}
894
895void AudioFileCache::Reader::setLoopRange (SampleRange newRange)
896{
897 loopStart = newRange.getStart();
898 loopLength = newRange.getLength();
899}
900
901bool AudioFileCache::Reader::readSamples (int numSamples,
902 juce::AudioBuffer<float>& destBuffer,
903 const juce::AudioChannelSet& destBufferChannels,
904 int startOffsetInDestBuffer,
905 const juce::AudioChannelSet& sourceBufferChannels,
906 int timeoutMs)
907{
908 jassert (numSamples < CachedFile::readAheadSamples); // this method fails unless broken down into chunks smaller than this
909 const auto numDestChans = destBuffer.getNumChannels();
910
911 // This may need to deal with the generic surround case if destBuffer number of channels > channelsToUse.size()
912 if (cache.engine.getEngineBehaviour().isDescriptionOfWaveDevicesSupported())
913 {
914 static constexpr int maxNumChannels = 32;
915 float* chans[maxNumChannels] = {};
916 auto numSourceChans = std::min (maxNumChannels, sourceBufferChannels.size());
917 int highestUsedSourceChan = 0;
918
919 for (int destIndex = 0; destIndex < numDestChans; ++destIndex)
920 {
921 auto destType = destBufferChannels.getTypeOfChannel (destIndex);
922 auto destData = destBuffer.getWritePointer (destIndex, startOffsetInDestBuffer);
923 auto sourceIndex = sourceBufferChannels.getChannelIndexForType (destType);
924
925 if (sourceIndex >= 0 && sourceIndex < maxNumChannels)
926 {
927 chans[sourceIndex] = destData;
928 highestUsedSourceChan = std::max (highestUsedSourceChan, sourceIndex);
929 }
930 else
931 {
932 juce::FloatVectorOperations::clear (destData, numSamples);
933 }
934 }
935
936 if (readSamples ((int**) chans, numSourceChans, 0, numSamples, timeoutMs))
937 {
938 bool isFloatingPoint = (file != nullptr) ? static_cast<CachedFile*> (file)->info.isFloatingPoint
939 : fallbackReader->usesFloatingPointData;
940
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);
945
946 return true;
947 }
948 }
949 else
950 {
951 float* chans[2] = {};
952 bool dupeChannel = false;
953
954 if (numDestChans > 1)
955 {
956 if (sourceBufferChannels.getChannelIndexForType (juce::AudioChannelSet::left) >= 0
957 && sourceBufferChannels.getChannelIndexForType (juce::AudioChannelSet::right) >= 0)
958 {
959 chans[0] = destBuffer.getWritePointer (0, startOffsetInDestBuffer);
960
961 if (getNumChannels() > 1)
962 chans[1] = destBuffer.getWritePointer (1, startOffsetInDestBuffer);
963 else
964 dupeChannel = true;
965 }
966 else if (sourceBufferChannels.getChannelIndexForType (juce::AudioChannelSet::left) >= 0)
967 {
968 chans[0] = destBuffer.getWritePointer (0, startOffsetInDestBuffer);
969 dupeChannel = true;
970 }
971 else
972 {
973 chans[1] = destBuffer.getWritePointer (1, startOffsetInDestBuffer);
974 dupeChannel = true;
975 }
976 }
977 else
978 {
979 if (sourceBufferChannels.getChannelIndexForType (juce::AudioChannelSet::left) >= 0 || getNumChannels() < 2)
980 chans[0] = destBuffer.getWritePointer (0, startOffsetInDestBuffer);
981 else
982 chans[1] = destBuffer.getWritePointer (0, startOffsetInDestBuffer);
983 }
984
985 if (readSamples ((int**) chans, 2, 0, numSamples, timeoutMs))
986 {
987 const bool isFloatingPoint = (file != nullptr) ? static_cast<CachedFile*> (file)->info.isFloatingPoint
988 : fallbackReader->usesFloatingPointData;
989
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);
994
995 if (dupeChannel)
996 {
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);
1001 }
1002
1003 return true;
1004 }
1005 }
1006
1007 return false;
1008}
1009
1010bool AudioFileCache::Reader::readSamples (int* const* destSamples, int numDestChannels,
1011 int startOffsetInDestBuffer, int numSamples, int timeoutMs)
1012{
1013 jassert (numSamples < CachedFile::readAheadSamples); // this method fails unless broken down into chunks smaller than this
1014 jassert (getReferenceCount() > 1 || file == nullptr); // may be being used after the cache has been deleted
1015 jassert (timeoutMs >= 0);
1016
1017 if (readPos < 0)
1018 {
1019 auto silence = (int) std::min (-readPos, (SampleCount) numSamples);
1020
1021 for (int i = numDestChannels; --i >= 0;)
1022 if (destSamples[i] != nullptr)
1023 juce::FloatVectorOperations::clear ((float*) destSamples[i], silence);
1024
1025 startOffsetInDestBuffer += silence;
1026 numSamples -= silence;
1027 readPos += silence;
1028
1029 if (numSamples <= 0)
1030 return true;
1031 }
1032
1033 bool allOk = true;
1034 const ScopedFileRead sfr (cache);
1035
1036 if (loopLength == 0)
1037 {
1038 if (auto cf = static_cast<CachedFile*> (file))
1039 {
1040 allOk = cf->read (readPos, destSamples, numDestChannels, startOffsetInDestBuffer, numSamples, timeoutMs);
1041 }
1042 else
1043 {
1044 fallbackReader->setReadTimeout (timeoutMs);
1045 allOk = fallbackReader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, readPos, numSamples);
1046 }
1047
1048 readPos += numSamples;
1049 }
1050 else if (loopLength > 1)
1051 {
1052 while (numSamples > 0)
1053 {
1054 jassert (juce::isPositiveAndBelow (readPos.load() - loopStart.load(), loopLength.load()));
1055
1056 auto numToRead = (int) std::min ((SampleCount) numSamples, loopStart + loopLength - readPos);
1057
1058 if (auto cf = static_cast<CachedFile*> (file))
1059 {
1060 allOk = cf->read (readPos, destSamples, numDestChannels, startOffsetInDestBuffer, numToRead, timeoutMs) && allOk;
1061 }
1062 else
1063 {
1064 fallbackReader->setReadTimeout (timeoutMs);
1065 allOk = fallbackReader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, readPos, numToRead) && allOk;
1066 }
1067
1068 readPos += numToRead;
1069
1070 if (readPos >= loopStart + loopLength)
1071 readPos -= loopLength;
1072
1073 startOffsetInDestBuffer += numToRead;
1074 numSamples -= numToRead;
1075 }
1076
1077 return allOk;
1078 }
1079 else
1080 {
1081 clearSetOfChannels (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples);
1082 }
1083
1084 if (! allOk)
1085 cache.cacheMissed = true;
1086
1087 return allOk;
1088}
1089
1090bool AudioFileCache::Reader::getRange (int numSamples, float& lmax, float& lmin, float& rmax, float& rmin, int timeoutMs)
1091{
1092 jassert (getReferenceCount() > 1 || file == nullptr); // may be being used after the cache has been deleted
1093
1094 bool ok;
1095
1096 if (auto cf = static_cast<CachedFile*> (file))
1097 {
1098 ok = cf->getRange (readPos, numSamples, lmax, lmin, rmax, rmin, timeoutMs);
1099 }
1100 else
1101 {
1102 fallbackReader->setReadTimeout (timeoutMs);
1103 fallbackReader->readMaxLevels (readPos, numSamples, lmin, lmax, rmin, rmax);
1104 ok = true;
1105 }
1106
1107 readPos += numSamples;
1108 return ok;
1109}
1110
1111//==============================================================================
1113{
1114 CacheAudioFormatReader (const AudioFile& file)
1115 : juce::AudioFormatReader (nullptr, "Cache")
1116 {
1117 auto info = file.getInfo();
1118
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;
1125
1126 reader = file.engine->getAudioFileManager().cache.createReader (file);
1127 }
1128
1130
1131 void readMaxLevels (juce::int64 startSample, juce::int64 numSamples,
1132 float& lowestLeft, float& highestLeft,
1133 float& lowestRight, float& highestRight) override
1134 {
1135 reader->setReadPosition (startSample);
1136 reader->getRange ((int) numSamples, highestLeft, lowestLeft, highestRight, lowestRight, -1);
1137 }
1138
1139 bool readSamples (int* const* destSamples, int numDestChannels,
1140 int startOffsetInDestBuffer, juce::int64 startSampleInFile,
1141 int numSamples) override
1142 {
1143 reader->setReadPosition (startSampleInFile);
1144 return reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, numSamples, -1);
1145 }
1146
1147private:
1149
1151};
1152
1153}} // namespace tracktion { inline namespace engine
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
void clear()
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
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
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)
int nextInt() noexcept
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 startThread()
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)
T exchange(T... args)
T get(T... args)
T is_pointer_v
#define jassert(expression)
#define DBG(textToWrite)
#define JUCE_DECLARE_NON_COPYABLE(className)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
typedef int
T load(T... args)
T max(T... args)
T min(T... args)
T move(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
long long int64
T release(T... args)
T sample(T... args)
typedef uint32_t
T store(T... args)
Represents a duration in real-life time.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.