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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AudioFile.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
14static inline HashCode getAudioFileHash (const juce::File& file) noexcept
15{
16 return file.getFullPathName().hashCode64();
17}
18
19//==============================================================================
20AudioFile::AudioFile (Engine& e, const juce::File& f) noexcept
21 : engine (&e), file (f), hash (getAudioFileHash (f))
22{
23}
24
25AudioFile::AudioFile (const AudioFile& other) noexcept
26 : engine (other.engine), file (other.file), hash (other.hash)
27{
28}
29
30AudioFile& AudioFile::operator= (const AudioFile& other) noexcept
31{
32 file = other.file;
33 hash = other.hash;
34 return *this;
35}
36
37AudioFile::~AudioFile() {}
38
39AudioFileInfo AudioFile::getInfo() const
40{
42
43 if (file == juce::File())
44 return AudioFileInfo (*engine);
45
46 return engine->getAudioFileManager().getInfo (*this);
47}
48
49juce::String AudioFileInfo::getLongDescription() const
50{
51 juce::String desc;
52
53 if (sampleRate > 0.0)
54 {
55 desc << juce::String ((sampleRate / 1000.0) + 0.0001, 1) << " kHz, ";
56
57 if (bitsPerSample > 0) desc << bitsPerSample << " bit ";
58
59 desc << (numChannels == 1 ? TRANS("mono") : TRANS("stereo"))
60 << ", " << juce::RelativeTime (getLengthInSeconds()).getDescription();
61
63
64 const int numBeats = juce::roundToInt (loopInfo.getNumBeats());
65 if (numBeats == 1)
66 items.add (TRANS("1 beat"));
67 else if (numBeats > 1)
68 items.add (TRANS("123 beats").replace ("123", juce::String (numBeats)));
69
70 if (loopInfo.isLoopable())
71 {
72 double bpm = loopInfo.getNumBeats() / (getLengthInSeconds() / 60.0);
73 items.add (juce::String (bpm, 1) + " bpm");
74 }
75
76 if (loopInfo.getNumerator() && loopInfo.getDenominator())
77 items.add (juce::String (loopInfo.getNumerator()) + "/" + juce::String (loopInfo.getDenominator()));
78
79 if (loopInfo.getRootNote() != -1)
80 items.add (juce::MidiMessage::getMidiNoteName (loopInfo.getRootNote(), true, true,
81 engine->getEngineBehaviour().getMiddleCOctave()));
82
83 if (items.size() > 0)
84 desc << "\n" << items.joinIntoString (", ");
85 }
86
87 return desc;
88}
89
90bool AudioFile::isValid() const { return hash != 0 && getSampleRate() > 0; }
91SampleCount AudioFile::getLengthInSamples() const { return getInfo().lengthInSamples; }
92double AudioFile::getLength() const { return getInfo().getLengthInSeconds(); }
93int AudioFile::getNumChannels() const { return getInfo().numChannels; }
94double AudioFile::getSampleRate() const { return getInfo().sampleRate; }
95int AudioFile::getBitsPerSample() const { return getInfo().bitsPerSample; }
96bool AudioFile::isFloatingPoint() const { return getInfo().isFloatingPoint; }
97juce::StringPairArray AudioFile::getMetadata() const { return getInfo().metadata; }
98juce::AudioFormat* AudioFile::getFormat() const { return getInfo().format; }
99
100bool AudioFile::moveToTrash() const
101{
103
104 auto& afm = engine->getAudioFileManager();
105 afm.checkFileForChangesAsync (*this);
106 afm.releaseFile (*this);
107
108 return file.moveToTrash();
109}
110
111bool AudioFile::deleteFile() const
112{
114
115 auto& afm = engine->getAudioFileManager();
116 afm.checkFileForChangesAsync (*this);
117 afm.releaseFile (*this);
118
119 bool ok = file.deleteFile();
120 jassert (ok);
121 return ok;
122}
123
124bool AudioFile::deleteFiles (Engine& engine, const juce::Array<juce::File>& files)
125{
126 bool allOK = true;
127
128 for (auto& f : files)
129 if (! AudioFile (engine, f).deleteFile())
130 allOK = false;
131
132 return allOK;
133}
134
135bool AudioFile::isWavFile() const { return file.hasFileExtension ("wav;bwav;bwf"); }
136bool AudioFile::isAiffFile() const { return file.hasFileExtension ("aiff;aif"); }
137bool AudioFile::isOggFile() const { return file.hasFileExtension ("ogg"); }
138bool AudioFile::isMp3File() const { return file.hasFileExtension ("mp3"); }
139bool AudioFile::isFlacFile() const { return file.hasFileExtension ("flac"); }
140bool AudioFile::isRexFile() const { return file.hasFileExtension ("rex;rx2;rcy"); }
141
142
143//==============================================================================
144const int numSamplesPerFlush = 48000 * 6;
145
146AudioFileWriter::AudioFileWriter (const AudioFile& f,
147 juce::AudioFormat* formatToUse,
148 int numChannels,
149 double sampleRate,
150 int bitsPerSample,
151 const juce::StringPairArray& metadata,
152 int quality)
153 : file (f), samplesUntilFlush (numSamplesPerFlush)
154{
156 f.engine->getAudioFileManager().releaseFile (file);
157
158 if (file.getFile().getParentDirectory().createDirectory())
159 {
160 const juce::ScopedLock sl (writerLock);
161 writer.reset (AudioFileUtils::createWriterFor (formatToUse, file.getFile(), sampleRate,
162 (unsigned int) numChannels, bitsPerSample,
163 metadata, quality));
164 }
165}
166
167AudioFileWriter::~AudioFileWriter()
168{
169 closeForWriting();
170}
171
172bool AudioFileWriter::isOpen() const noexcept { const juce::ScopedLock sl (writerLock); return writer != nullptr; }
173double AudioFileWriter::getSampleRate() const noexcept { jassert (isOpen()); const juce::ScopedLock sl (writerLock); return writer->getSampleRate(); }
174int AudioFileWriter::getNumChannels() const noexcept { jassert (isOpen()); const juce::ScopedLock sl (writerLock); return writer->getNumChannels(); }
175
176void AudioFileWriter::closeForWriting()
177{
178 {
179 const juce::ScopedLock sl (writerLock);
180 writer.reset();
181 }
182
183 auto& audioFileManager = file.engine->getAudioFileManager();
184 audioFileManager.releaseFile (file);
185 audioFileManager.checkFileForChanges (file);
186}
187
188bool AudioFileWriter::appendBuffer (juce::AudioBuffer<float>& buffer, int num)
189{
190 num = std::min (num, buffer.getNumSamples());
191 const juce::ScopedLock sl (writerLock);
192
193 if (writer != nullptr && writer->writeFromAudioSampleBuffer (buffer, 0, num))
194 {
195 samplesUntilFlush -= num;
196
197 if (samplesUntilFlush <= 0)
198 {
199 samplesUntilFlush = numSamplesPerFlush;
200 writer->flush();
201 }
202
203 return true;
204 }
205
206 return false;
207}
208
209bool AudioFileWriter::appendBuffer (const int** buffer, int num)
210{
211 const juce::ScopedLock sl (writerLock);
212 return writer != nullptr && writer->write (buffer, num);
213}
214
215bool AudioFileWriter::writeFromAudioReader (juce::AudioFormatReader& reader,
216 SampleCount startSample,
217 SampleCount numSamples)
218{
219 const juce::ScopedLock sl (writerLock);
220 return writer != nullptr && writer->writeFromAudioReader (reader, startSample, numSamples);
221}
222
223//==============================================================================
224AudioProxyGenerator::GeneratorJob::GeneratorJob (const AudioFile& p)
225 : ThreadPoolJobWithProgress ("proxy"), proxy (p)
226{
227}
228
229AudioProxyGenerator::GeneratorJob::~GeneratorJob()
230{
232 callBlocking ([this] { proxy.engine->getAudioFileManager().validateFile (proxy, false); });
233}
234
235juce::ThreadPoolJob::JobStatus AudioProxyGenerator::GeneratorJob::runJob()
236{
238
239 auto& afm = proxy.engine->getAudioFileManager();
241 proxy.deleteFile();
242
243 if (render())
244 afm.checkFileForChangesAsync (proxy);
245 else
246 proxy.deleteFile();
247
248 progress = 1.0f;
249
250 afm.proxyGenerator.removeFinishedJob (this);
251 return jobHasFinished;
252}
253
254//==============================================================================
255AudioProxyGenerator::AudioProxyGenerator()
256{
257}
258
259AudioProxyGenerator::~AudioProxyGenerator()
260{
262}
263
264AudioProxyGenerator::GeneratorJob* AudioProxyGenerator::findJob (const AudioFile& proxy) const noexcept
265{
266 for (auto j : activeJobs)
267 if (j->proxy == proxy)
268 return j;
269
270 return {};
271}
272
273static bool checkProxyStatus (const AudioFile& f)
274{
275 if (f.getFile().existsAsFile())
276 {
277 if (f.isValid())
278 return true;
279
280 f.deleteFile();
281 }
282
283 return false;
284}
285
286void AudioProxyGenerator::beginJob (GeneratorJob* j)
287{
290
291 if (! checkProxyStatus (job->proxy))
292 {
293 const juce::ScopedLock sl (jobListLock);
294
295 if (findJob (job->proxy) == nullptr)
296 {
297 job->proxy.engine->getBackgroundJobs().addJob (j, true);
298 activeJobs.add (job.release());
299 }
300 }
301}
302
303bool AudioProxyGenerator::isProxyBeingGenerated (const AudioFile& proxyFile) const noexcept
304{
305 const juce::ScopedLock sl (jobListLock);
306 return findJob (proxyFile) != nullptr;
307}
308
309float AudioProxyGenerator::getProportionComplete (const AudioFile& proxyFile) const noexcept
310{
311 const juce::ScopedLock sl (jobListLock);
312
313 if (auto j = findJob (proxyFile))
314 return j->progress;
315
316 return 1.0f;
317}
318
319void AudioProxyGenerator::removeFinishedJob (GeneratorJob* j)
320{
321 const juce::ScopedLock sl (jobListLock);
322 activeJobs.removeAllInstancesOf (j);
323}
324
325void AudioProxyGenerator::deleteProxy (const AudioFile& proxyFile)
326{
328 GeneratorJob* j = nullptr;
329
330 {
331 const juce::ScopedLock sl (jobListLock);
332 j = findJob (proxyFile);
333 }
334
335 if (j != nullptr)
336 proxyFile.engine->getBackgroundJobs().removeJob (j, true, 10000);
337
338 proxyFile.deleteFile();
339}
340
341
342//==============================================================================
343AudioFileInfo::AudioFileInfo (Engine& e)
344 : engine (&e), loopInfo (e)
345{
346}
347
348AudioFileInfo::AudioFileInfo (const AudioFile& file, juce::AudioFormatReader* reader, juce::AudioFormat* f)
349 : engine (file.engine), hashCode (file.getHash()), format (f),
350 fileModificationTime (file.getFile().getLastModificationTime()),
351 loopInfo (*file.engine, reader, f, file.getFile())
352{
353 if (reader != nullptr)
354 {
355 wasParsedOk = true;
356 sampleRate = reader->sampleRate;
357 lengthInSamples = reader->lengthInSamples;
358 numChannels = (int) reader->numChannels;
359 bitsPerSample = (int) reader->bitsPerSample;
360 isFloatingPoint = reader->usesFloatingPointData;
361 needsCachedProxy = dynamic_cast<juce::WavAudioFormat*> (format) == nullptr
362 && dynamic_cast<juce::AiffAudioFormat*> (format) == nullptr
363 && dynamic_cast<FloatAudioFormat*> (format) == nullptr;
364 metadata = reader->metadataValues;
365 }
366 else
367 {
368 wasParsedOk = false;
369 format = nullptr;
370 sampleRate = 0;
371 lengthInSamples = 0;
372 numChannels = 0;
373 bitsPerSample = 0;
374 isFloatingPoint = false;
375 needsCachedProxy = false;
376 }
377}
378
379AudioFileInfo AudioFileInfo::parse (const AudioFile& file)
380{
381 if (! file.isNull())
382 {
383 juce::AudioFormat* format = nullptr;
384
385 if (auto reader = std::unique_ptr<juce::AudioFormatReader> (AudioFileUtils::createReaderFindingFormat (*file.engine, file.getFile(), format)))
386 return AudioFileInfo (file, reader.get(), format);
387 }
388
389 return AudioFileInfo (file, nullptr, nullptr);
390}
391
392//==============================================================================
394{
395public:
397 #if JUCE_DEBUG
399 #else
401 #endif
402 , engine (e)
403 {
404 }
405
406 void saveNewlyFinishedThumbnail (const juce::AudioThumbnailBase& thumb, juce::int64 hash) override
407 {
409 auto st = getActiveSmartThumbnail (thumb);
410 auto thumbFile = getThumbFile (st, hash);
411
412 if (thumbFile.deleteFile())
413 {
414 thumbFile.getParentDirectory().createDirectory();
415
416 juce::FileOutputStream fo (thumbFile);
417
418 if (! fo.openedOk())
419 return;
420
421 thumb.saveTo (fo);
422 }
423 }
424
425 bool loadNewThumb (juce::AudioThumbnailBase& thumb, juce::int64 hash) override
426 {
428 auto st = getActiveSmartThumbnail (thumb);
429 auto thumbFile = getThumbFile (st, hash);
430
431 if (st != nullptr
432 && st->file.getFile().getLastModificationTime() > thumbFile.getLastModificationTime()
434 {
435 thumbFile.deleteFile();
436 return false;
437 }
438
439 juce::FileInputStream fin (thumbFile);
440 return fin.openedOk() && thumb.loadFrom (fin);
441 }
442
443private:
444 Engine& engine;
445
446 juce::File getThumbFolder (Edit* edit) const
447 {
448 if (edit != nullptr)
449 return edit->getTempDirectory (false);
450
451 return engine.getTemporaryFileManager().getThumbnailsFolder();
452 }
453
454 SmartThumbnail* getActiveSmartThumbnail (const juce::AudioThumbnailBase& thumb)
455 {
456 auto& map = engine.getAudioFileManager().thumbnailMap;
457
458 if (auto found = map.find (&thumb); found != map.end())
459 return found->second;
460
461 return nullptr;
462 }
463
464 juce::File getThumbFile (const SmartThumbnail* st, HashCode hash) const
465 {
466 auto thumbFolder = getThumbFolder (st != nullptr ? st->edit : nullptr);
467
468 return thumbFolder.getChildFile ("thumbnail_" + juce::String::toHexString (hash) + ".thumb");
469 }
470};
471
472//==============================================================================
474{
475 KnownFile (const AudioFile& f)
476 : file (f), info (AudioFileInfo::parse (file))
477 {
478 }
479
480 AudioFile file;
481 AudioFileInfo info;
482
484};
485
486//==============================================================================
487enum { initialTimerDelay = 10 };
488
489bool SmartThumbnail::enabled = true;
490
492 : SmartThumbnail (e, f, componentToRepaint, ed,
493 e.getUIBehaviour().createAudioThumbnail (256,
494 e.getAudioFileFormatManager().readFormatManager,
495 e.getAudioFileManager().getAudioThumbnailCache()))
496{
497}
498
501 : file (f), engine (e), edit (ed),
502 thumbnail (std::move (thumbnailToUse)),
503 component (componentToRepaint)
504{
505 TRACKTION_ASSERT_MESSAGE_THREAD
506 assert (thumbnail && "thumbnail must be valid!");
507 startTimer (initialTimerDelay);
508 engine.getAudioFileManager().activeThumbnails.add (this);
509 engine.getAudioFileManager().thumbnailMap[thumbnail.get()] = this;
510
511 // Ensure the AudioFileManager knows about this type of thumbnail
512 auto& thumbRef = *thumbnail; // Work around a clang warning
513 const auto thumbTypeHashCode = std::hash<std::string>{} (typeid (thumbRef).name());
514 engine.getAudioFileManager().thumbnailTypeHashes.insert (thumbTypeHashCode);
515}
516
518{
519 TRACKTION_ASSERT_MESSAGE_THREAD
520
521 engine.getAudioFileManager().thumbnailMap.erase (thumbnail.get());
522 engine.getAudioFileManager().activeThumbnails.removeAllInstancesOf (this);
523 thumbnail->clear();
524}
525
527{
528 TRACKTION_ASSERT_MESSAGE_THREAD
529
530 for (auto thumb : engine.getAudioFileManager().activeThumbnails)
531 if (! thumb->isFullyLoaded())
532 return false;
533
534 return true;
535}
536
538{
539 if (file != newFile)
540 {
541 file = newFile;
542
543 audioFileChanged();
544 component.repaint();
545 }
546}
547
548//==============================================================================
550 TimeRange time, int channelNum, float verticalZoomFactor)
551{
552 thumbnail->drawChannel (g, r,
553 time.getStart().inSeconds(), time.getEnd().inSeconds(),
554 channelNum, verticalZoomFactor);
555}
556
558 TimeRange time, float verticalZoomFactor)
559{
560 thumbnail->drawChannels (g, r,
561 time.getStart().inSeconds(), time.getEnd().inSeconds(),
562 verticalZoomFactor);
563}
564
566{
567 if (auto sampleRate = file.getSampleRate(); sampleRate > 0)
568 {
569 const auto totalSamples = toSamples (TimePosition::fromSeconds (getTotalLength()), sampleRate);
570 return juce::jlimit (0.0, 1.0, getNumSamplesFinished() / (double) std::max ((SampleCount) 1, totalSamples));
571 }
572
573 return 0.0;
574}
575
576//==============================================================================
577void SmartThumbnail::releaseFile()
578{
579 clear();
580 thumbnailIsInvalid = true;
581 juce::MessageManager::callAsync ([ref = juce::WeakReference<SmartThumbnail> (this), this]() mutable
582 {
583 if (ref != nullptr)
584 startTimer (400);
585 });
586}
587
588void SmartThumbnail::createThumbnailReader()
589{
590 if (enabled)
591 {
592 // This hashing takes in to account the type of the thumb to avoid clashes if
593 // you're using different types for different displays
594 // N.B. if this changes, AudioFileManager::callListeners will also need to be updated
595 auto& thumbRef = *thumbnail; // Work around a clang warning
596 const auto thumbTypeHashCode = std::hash<std::string>{} (typeid (thumbRef).name());
597 const auto hashCode = static_cast<juce::int64> (hash ((size_t) file.getHash(), thumbTypeHashCode));
598
599 // this breaks thumbnails in 64-bit mode
600 //setReader (new CacheAudioFormatReader (file), hashCode);
601
602 setReader (AudioFileUtils::createReaderFor (engine, file.getFile()), hashCode);
603 thumbnailIsInvalid = false;
604 }
605 else
606 {
607 thumbnailIsInvalid = true;
608 }
609}
610
611void SmartThumbnail::audioFileChanged()
612{
614 auto& proxyGen = engine.getAudioFileManager().proxyGenerator;
615
616 wasGeneratingProxy = proxyGen.isProxyBeingGenerated (file);
617
618 clear();
619
620 if (file.getFile().exists())
621 createThumbnailReader();
622 else
623 thumbnailIsInvalid = true;
624
625 lastProgress = 0.0f;
626 component.repaint();
627 startTimer (200);
628}
629
630//==============================================================================
631void SmartThumbnail::clear()
632{
633 thumbnail->clear();
634}
635
636bool SmartThumbnail::setSource (juce::InputSource* source)
637{
638 return thumbnail->setSource (source);
639}
640
641void SmartThumbnail::setReader (juce::AudioFormatReader* reader, juce::int64 hashCode)
642{
643 thumbnail->setReader (reader, hashCode);
644}
645
646bool SmartThumbnail::loadFrom (juce::InputStream& stream)
647{
648 return thumbnail->loadFrom (stream);
649}
650
651void SmartThumbnail::saveTo (juce::OutputStream& stream) const
652{
653 thumbnail->saveTo (stream);
654}
655
656int SmartThumbnail::getNumChannels() const noexcept
657{
658 return thumbnail->getNumChannels();
659}
660
661double SmartThumbnail::getTotalLength() const noexcept
662{
663 return thumbnail->getTotalLength();
664}
665
667 const juce::Rectangle<int>& r,
668 double startTimeSeconds,
669 double endTimeSeconds,
670 int channelNum,
671 float verticalZoomFactor)
672{
673 thumbnail->drawChannel (g, r,
674 startTimeSeconds,
675 endTimeSeconds,
676 channelNum,
677 verticalZoomFactor);
678}
679
681 const juce::Rectangle<int>& r,
682 double startTimeSeconds,
683 double endTimeSeconds,
684 float verticalZoomFactor)
685{
686 thumbnail->drawChannels (g, r,
687 startTimeSeconds,
688 endTimeSeconds,
689 verticalZoomFactor);
690}
691
692bool SmartThumbnail::isFullyLoaded() const noexcept
693{
694 return thumbnail->isFullyLoaded();
695}
696
697juce::int64 SmartThumbnail::getNumSamplesFinished() const noexcept
698{
699 return thumbnail->getNumSamplesFinished();
700}
701
702float SmartThumbnail::getApproximatePeak() const
703{
704 return thumbnail->getApproximatePeak();
705}
706
707void SmartThumbnail::getApproximateMinMax (double startTime, double endTime, int channelIndex,
708 float& minValue, float& maxValue) const noexcept
709{
710 thumbnail->getApproximateMinMax (startTime, endTime, channelIndex,
711 minValue, maxValue);
712}
713
714juce::int64 SmartThumbnail::getHashCode() const
715{
716 return thumbnail->getHashCode();
717}
718
719void SmartThumbnail::reset (int numChannels, double sampleRate, juce::int64 totalSamplesInSource)
720{
721 thumbnail->reset (numChannels, sampleRate, totalSamplesInSource);
722}
723
724void SmartThumbnail::addBlock (juce::int64 sampleNumberInSource, const juce::AudioBuffer<float>& buffer,
725 int startOffsetInBuffer, int numSamples)
726{
727 thumbnail->addBlock (sampleNumberInSource, buffer,
728 startOffsetInBuffer, numSamples);
729}
730
731//==============================================================================
732void SmartThumbnail::timerCallback()
733{
735
736 auto& afm = engine.getAudioFileManager();
737 auto& proxyGen = afm.proxyGenerator;
738
739 if (getTimerInterval() == initialTimerDelay)
740 audioFileChanged();
741
742 const bool isGeneratingNow = proxyGen.isProxyBeingGenerated (file);
743
744 if (wasGeneratingProxy != isGeneratingNow || (thumbnailIsInvalid && file.getFile().exists()))
745 {
746 wasGeneratingProxy = isGeneratingNow;
747
748 if (! isGeneratingNow)
749 {
750 afm.checkFileForChanges (file);
751 createThumbnailReader();
752 }
753 else
754 {
755 thumbnailIsInvalid = true;
756 }
757
758 component.repaint();
759 }
760
761 if (isGeneratingNow || ! isFullyLoaded())
762 {
763 float progress = isGeneratingNow ? proxyGen.getProportionComplete (file)
765
766 if (lastProgress != progress)
767 {
768 lastProgress = progress;
769 component.repaint();
770 }
771 }
772 else if (! thumbnailIsInvalid || ! file.getFile().exists())
773 {
774 component.repaint();
775 stopTimer();
776 }
777}
778
779//==============================================================================
780AudioFileManager::AudioFileManager (Engine& e)
781 : engine (e), cache (e), thumbnailCache (std::make_unique<TracktionThumbnailCache> (e))
782{
783}
784
785AudioFileManager::~AudioFileManager()
786{
787 clearFiles();
788}
789
790AudioFileManager::KnownFile& AudioFileManager::findOrCreateKnown (const AudioFile& f)
791{
792 auto hash = f.getHash();
793 auto kf = knownFiles.find (hash);
794
795 if (kf != knownFiles.end())
796 return *kf->second.get();
797
798 knownFiles[hash] = std::make_unique<KnownFile> (f);
799 return *knownFiles[hash].get();
800}
801
802void AudioFileManager::clearFiles()
803{
805 const juce::ScopedLock sl (knownFilesLock);
806 knownFiles.clear();
807}
808
809void AudioFileManager::removeFile (HashCode hash)
810{
811 const juce::ScopedLock sl (knownFilesLock);
812
813 auto f = knownFiles.find (hash);
814
815 if (f != knownFiles.end())
816 knownFiles.erase (f);
817}
818
819AudioFile AudioFileManager::getAudioFile (ProjectItemID sourceID)
820{
821 return AudioFile (engine, engine.getProjectManager().findSourceFile (sourceID));
822}
823
824AudioFileInfo AudioFileManager::getInfo (const AudioFile& file)
825{
826 const juce::ScopedLock sl (knownFilesLock);
827 return findOrCreateKnown (file).info;
828}
829
830bool AudioFileManager::checkFileTime (KnownFile& f)
831{
832 if (! f.info.wasParsedOk
833 || f.info.fileModificationTime != f.file.getFile().getLastModificationTime())
834 {
835 f.info = AudioFileInfo::parse (f.file);
836 return true;
837 }
838
839 return false;
840}
841
842void AudioFileManager::checkFileForChanges (const AudioFile& file)
843{
845
846 bool changed = false;
847
848 {
849 const juce::ScopedLock sl (knownFilesLock);
850
851 auto f = knownFiles.find (file.getHash());
852
853 if (f != knownFiles.end())
854 changed = checkFileTime (*f->second);
855 }
856
857 if (changed)
858 {
859 releaseFile (file);
860 callListenersOnMessageThread (file);
861 }
862}
863
864void AudioFileManager::checkFilesForChanges()
865{
866 TRACKTION_ASSERT_MESSAGE_THREAD
867
868 juce::Array<AudioFile> changedFiles;
869
870 {
871 const juce::ScopedLock sl (knownFilesLock);
872
873 for (auto& f : knownFiles)
874 if (checkFileTime (*f.second))
875 changedFiles.add (f.second->file);
876 }
877
878 for (auto& f : changedFiles)
879 {
880 releaseFile (f);
881 callListeners (f);
882 }
883}
884
885void AudioFileManager::releaseAllFiles()
886{
887 cache.releaseAllFiles();
888
889 const juce::ScopedLock sl (activeThumbnailLock);
890
891 for (auto t : activeThumbnails)
892 t->releaseFile();
893}
894
895void AudioFileManager::releaseFile (const AudioFile& file)
896{
897 cache.releaseFile (file);
898
899 const juce::ScopedLock sl (activeThumbnailLock);
900
901 for (auto t : activeThumbnails)
902 if (t->file == file)
903 t->releaseFile();
904}
905
906void AudioFileManager::callListeners (const AudioFile& file)
907{
909 TRACKTION_ASSERT_MESSAGE_THREAD
910
911 for (auto h : thumbnailTypeHashes)
912 {
913 const auto hashCode = hash ((size_t) file.getHash(), h);
914 thumbnailCache->removeThumb (static_cast<juce::int64> (hashCode));
915 }
916
917 const juce::ScopedLock sl (activeThumbnailLock);
918
919 for (auto t : activeThumbnails)
920 if (t->file == file)
921 t->audioFileChanged();
922}
923
924void AudioFileManager::callListenersOnMessageThread (const AudioFile& file)
925{
926 if (juce::MessageManager::existsAndIsCurrentThread())
927 callListeners (file);
928 else
929 juce::MessageManager::callAsync ([file, eng = Engine::WeakRef (&engine)]
930 {
931 if (eng != nullptr)
932 eng->getAudioFileManager().callListeners (file);
933 });
934}
935
936void AudioFileManager::forceFileUpdate (const AudioFile& file)
937{
939 TRACKTION_ASSERT_MESSAGE_THREAD
940
941 // this doesn't check for file time and is used when files are changed rapidly such as when recording
942 const juce::ScopedLock sl (knownFilesLock);
943
944 auto f = knownFiles.find (file.getHash());
945
946 if (f != knownFiles.end())
947 {
948 f->second->info = AudioFileInfo::parse (f->second->file);
949 releaseFile (file);
950 callListeners (file);
951 }
952}
953
954void AudioFileManager::validateFile (const AudioFile& file, bool updateInfo)
955{
956 if (updateInfo)
957 {
958 getInfo (file);
959 forceFileUpdate (file);
960 }
961
962 cache.validateFile (file);
963}
964
965void AudioFileManager::checkFileForChangesAsync (const AudioFile& file)
966{
967 const juce::ScopedLock sl (knownFilesLock);
968 filesToCheck.addIfNotAlreadyThere (file);
970}
971
972void AudioFileManager::handleAsyncUpdate()
973{
975 AudioFile fileToCheck (engine);
976
977 {
978 const juce::ScopedLock sl (knownFilesLock);
979 fileToCheck = filesToCheck.getUnchecked (filesToCheck.size() - 1);
980 filesToCheck.removeLast();
981
982 if (filesToCheck.size() > 0)
984 }
985
986 if (! fileToCheck.isNull())
987 checkFileForChanges (fileToCheck);
988}
989
990}} // namespace tracktion { inline namespace engine
assert
int getNumSamples() const noexcept
StringPairArray metadataValues
unsigned int bitsPerSample
unsigned int numChannels
virtual bool loadFrom(InputStream &input)=0
virtual void saveTo(OutputStream &output) const=0
bool openedOk() const noexcept
bool openedOk() const noexcept
File getChildFile(StringRef relativeOrAbsolutePath) const
bool moveToTrash() const
bool deleteFile() const
bool hasFileExtension(StringRef extensionToTest) const
static void JUCE_CALLTYPE disableDenormalisedNumberSupport(bool shouldDisable=true) noexcept
bool isNull() const noexcept
static bool callAsync(std::function< void()> functionToCall)
static String getMidiNoteName(int noteNumber, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC)
static RelativeTime seconds(double seconds) noexcept
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
int size() const noexcept
void add(String stringToAdd)
static String toHexString(IntegerType number)
void stopTimer() noexcept
int getTimerInterval() const noexcept
void startTimer(int intervalInMilliseconds) noexcept
The Tracktion Edit class!
juce::File getTempDirectory(bool createIfNonExistent) const
Returns the temp directory the Edit it using.
The Engine is the central class for all tracktion sessions.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
TemporaryFileManager & getTemporaryFileManager() const
Returns the TemporaryFileManager allowing to handle the default app and user temporary folders.
int getDenominator() const
Returns the denominator of the object.
bool isLoopable() const
Returns true if this can be looped.
int getRootNote() const
Returns the root note of the object.
double getNumBeats() const
Returns the number of beats.
int getNumerator() const
Returns the numerator of the object.
SmartThumnail automatically tracks changes to an AudioFile and will update its cache if the file chan...
double getProportionComplete() const noexcept
Returns the proportion of the thumbnail that has been generated.
static bool areThumbnailsFullyLoaded(Engine &)
Returns true if any thumbnails are currently being generated for the given Edit.
void drawChannels(juce::Graphics &, juce::Rectangle< int >, TimeRange, float verticalZoomFactor)
Draws all of the channels, optionally using a hi-res algorithm.
void drawChannel(juce::Graphics &, juce::Rectangle< int >, TimeRange, int channelNum, float verticalZoomFactor)
Draws one of the channels, optionally using a hi-res algorithm.
void setNewFile(const AudioFile &)
Sets a new file to display.
SmartThumbnail(Engine &, const AudioFile &, juce::Component &componentToRepaint, Edit *)
Creates a SmartThumbnail for an AudioFile which will automatically repaint a Component as it it loade...
void prepareForJobDeletion()
Call this in your sub-class destructor to to remvoe it from the manager queue before this class's des...
T format(T... args)
T get(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
typedef int
T make_unique(T... args)
typedef float
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
long long int64
size_t hash(size_t seed, const T &v)
Hashes a type with a given seed and returns the new hash value.
T reset(T... args)
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.