11namespace tracktion {
inline namespace engine
14static inline HashCode getAudioFileHash (
const juce::File& file)
noexcept
16 return file.getFullPathName().hashCode64();
21 : engine (&e), file (f),
hash (getAudioFileHash (f))
25AudioFile::AudioFile (
const AudioFile& other) noexcept
26 : engine (other.engine), file (other.file),
hash (other.hash)
30AudioFile& AudioFile::operator= (
const AudioFile& other)
noexcept
37AudioFile::~AudioFile() {}
39AudioFileInfo AudioFile::getInfo()
const
44 return AudioFileInfo (*engine);
55 desc <<
juce::String ((sampleRate / 1000.0) + 0.0001, 1) <<
" kHz, ";
57 if (bitsPerSample > 0) desc << bitsPerSample <<
" bit ";
59 desc << (numChannels == 1 ?
TRANS(
"mono") :
TRANS(
"stereo"))
60 <<
", " <<
juce::RelativeTime (getLengthInSeconds()).getDescription();
67 else if (numBeats > 1)
72 double bpm = loopInfo.
getNumBeats() / (getLengthInSeconds() / 60.0);
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; }
100bool AudioFile::moveToTrash()
const
105 afm.checkFileForChangesAsync (*
this);
106 afm.releaseFile (*
this);
111bool AudioFile::deleteFile()
const
116 afm.checkFileForChangesAsync (*
this);
117 afm.releaseFile (*
this);
128 for (
auto& f : files)
129 if (! AudioFile (engine, f).deleteFile())
135bool AudioFile::isWavFile()
const {
return file.
hasFileExtension (
"wav;bwav;bwf"); }
136bool AudioFile::isAiffFile()
const {
return file.
hasFileExtension (
"aiff;aif"); }
139bool AudioFile::isFlacFile()
const {
return file.
hasFileExtension (
"flac"); }
140bool AudioFile::isRexFile()
const {
return file.
hasFileExtension (
"rex;rx2;rcy"); }
144const int numSamplesPerFlush = 48000 * 6;
146AudioFileWriter::AudioFileWriter (
const AudioFile& f,
153 : file (f), samplesUntilFlush (numSamplesPerFlush)
156 f.engine->getAudioFileManager().releaseFile (file);
158 if (file.getFile().getParentDirectory().createDirectory())
161 writer.reset (AudioFileUtils::createWriterFor (formatToUse, file.getFile(), sampleRate,
162 (
unsigned int) numChannels, bitsPerSample,
167AudioFileWriter::~AudioFileWriter()
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(); }
176void AudioFileWriter::closeForWriting()
183 auto& audioFileManager = file.engine->getAudioFileManager();
184 audioFileManager.releaseFile (file);
185 audioFileManager.checkFileForChanges (file);
193 if (writer !=
nullptr && writer->writeFromAudioSampleBuffer (buffer, 0, num))
195 samplesUntilFlush -= num;
197 if (samplesUntilFlush <= 0)
199 samplesUntilFlush = numSamplesPerFlush;
209bool AudioFileWriter::appendBuffer (
const int** buffer,
int num)
212 return writer !=
nullptr && writer->write (buffer, num);
216 SampleCount startSample,
217 SampleCount numSamples)
220 return writer !=
nullptr && writer->writeFromAudioReader (reader, startSample, numSamples);
224AudioProxyGenerator::GeneratorJob::GeneratorJob (
const AudioFile& p)
229AudioProxyGenerator::GeneratorJob::~GeneratorJob()
232 callBlocking ([
this] { proxy.engine->
getAudioFileManager().validateFile (proxy,
false); });
239 auto& afm = proxy.engine->getAudioFileManager();
244 afm.checkFileForChangesAsync (proxy);
250 afm.proxyGenerator.removeFinishedJob (
this);
251 return jobHasFinished;
255AudioProxyGenerator::AudioProxyGenerator()
259AudioProxyGenerator::~AudioProxyGenerator()
264AudioProxyGenerator::GeneratorJob* AudioProxyGenerator::findJob (
const AudioFile& proxy)
const noexcept
266 for (
auto j : activeJobs)
267 if (j->proxy == proxy)
273static bool checkProxyStatus (
const AudioFile& f)
275 if (f.getFile().existsAsFile())
286void AudioProxyGenerator::beginJob (GeneratorJob* j)
291 if (! checkProxyStatus (job->proxy))
295 if (findJob (job->proxy) ==
nullptr)
297 job->proxy.engine->getBackgroundJobs().addJob (j,
true);
298 activeJobs.add (job.release());
303bool AudioProxyGenerator::isProxyBeingGenerated (
const AudioFile& proxyFile)
const noexcept
306 return findJob (proxyFile) !=
nullptr;
309float AudioProxyGenerator::getProportionComplete (
const AudioFile& proxyFile)
const noexcept
313 if (
auto j = findJob (proxyFile))
319void AudioProxyGenerator::removeFinishedJob (GeneratorJob* j)
322 activeJobs.removeAllInstancesOf (j);
325void AudioProxyGenerator::deleteProxy (
const AudioFile& proxyFile)
328 GeneratorJob* j =
nullptr;
332 j = findJob (proxyFile);
336 proxyFile.engine->getBackgroundJobs().removeJob (j,
true, 10000);
338 proxyFile.deleteFile();
343AudioFileInfo::AudioFileInfo (
Engine& e)
344 : engine (&e), loopInfo (e)
349 : engine (file.engine), hashCode (file.getHash()),
format (f),
350 fileModificationTime (file.getFile().getLastModificationTime()),
351 loopInfo (*file.engine, reader, f, file.getFile())
353 if (reader !=
nullptr)
363 &&
dynamic_cast<FloatAudioFormat*
> (
format) ==
nullptr;
374 isFloatingPoint =
false;
375 needsCachedProxy =
false;
379AudioFileInfo AudioFileInfo::parse (
const AudioFile& file)
386 return AudioFileInfo (file, reader.get(), format);
389 return AudioFileInfo (file,
nullptr,
nullptr);
409 auto st = getActiveSmartThumbnail (thumb);
410 auto thumbFile = getThumbFile (st, hash);
412 if (thumbFile.deleteFile())
414 thumbFile.getParentDirectory().createDirectory();
428 auto st = getActiveSmartThumbnail (thumb);
429 auto thumbFile = getThumbFile (st, hash);
432 && st->file.getFile().getLastModificationTime() > thumbFile.getLastModificationTime()
435 thumbFile.deleteFile();
458 if (
auto found = map.find (&thumb); found != map.end())
459 return found->second;
466 auto thumbFolder = getThumbFolder (st !=
nullptr ? st->edit :
nullptr);
476 : file (f), info (AudioFileInfo::parse (file))
487enum { initialTimerDelay = 10 };
489bool SmartThumbnail::enabled =
true;
493 e.getUIBehaviour().createAudioThumbnail (256,
494 e.getAudioFileFormatManager().readFormatManager,
495 e.getAudioFileManager().getAudioThumbnailCache()))
501 : file (f), engine (e), edit (ed),
502 thumbnail (
std::move (thumbnailToUse)),
503 component (componentToRepaint)
505 TRACKTION_ASSERT_MESSAGE_THREAD
506 assert (thumbnail &&
"thumbnail must be valid!");
508 engine.getAudioFileManager().activeThumbnails.add (
this);
509 engine.getAudioFileManager().thumbnailMap[thumbnail.
get()] =
this;
512 auto& thumbRef = *thumbnail;
514 engine.getAudioFileManager().thumbnailTypeHashes.insert (thumbTypeHashCode);
519 TRACKTION_ASSERT_MESSAGE_THREAD
521 engine.getAudioFileManager().thumbnailMap.erase (thumbnail.
get());
522 engine.getAudioFileManager().activeThumbnails.removeAllInstancesOf (
this);
528 TRACKTION_ASSERT_MESSAGE_THREAD
531 if (! thumb->isFullyLoaded())
550 TimeRange time,
int channelNum,
float verticalZoomFactor)
552 thumbnail->drawChannel (g, r,
553 time.getStart().inSeconds(),
time.getEnd().inSeconds(),
554 channelNum, verticalZoomFactor);
558 TimeRange time,
float verticalZoomFactor)
560 thumbnail->drawChannels (g, r,
561 time.getStart().inSeconds(),
time.getEnd().inSeconds(),
567 if (
auto sampleRate = file.getSampleRate(); sampleRate > 0)
569 const auto totalSamples = toSamples (TimePosition::fromSeconds (getTotalLength()), sampleRate);
570 return juce::jlimit (0.0, 1.0, getNumSamplesFinished() / (
double)
std::max ((SampleCount) 1, totalSamples));
577void SmartThumbnail::releaseFile()
580 thumbnailIsInvalid =
true;
588void SmartThumbnail::createThumbnailReader()
595 auto& thumbRef = *thumbnail;
597 const auto hashCode =
static_cast<juce::int64> (
hash ((
size_t) file.getHash(), thumbTypeHashCode));
602 setReader (AudioFileUtils::createReaderFor (engine, file.getFile()), hashCode);
603 thumbnailIsInvalid =
false;
607 thumbnailIsInvalid =
true;
611void SmartThumbnail::audioFileChanged()
614 auto& proxyGen = engine.getAudioFileManager().proxyGenerator;
616 wasGeneratingProxy = proxyGen.isProxyBeingGenerated (file);
620 if (file.getFile().exists())
621 createThumbnailReader();
623 thumbnailIsInvalid =
true;
631void SmartThumbnail::clear()
638 return thumbnail->setSource (source);
643 thumbnail->setReader (reader, hashCode);
648 return thumbnail->loadFrom (stream);
653 thumbnail->saveTo (stream);
656int SmartThumbnail::getNumChannels() const noexcept
658 return thumbnail->getNumChannels();
661double SmartThumbnail::getTotalLength() const noexcept
663 return thumbnail->getTotalLength();
668 double startTimeSeconds,
669 double endTimeSeconds,
671 float verticalZoomFactor)
673 thumbnail->drawChannel (g, r,
682 double startTimeSeconds,
683 double endTimeSeconds,
684 float verticalZoomFactor)
686 thumbnail->drawChannels (g, r,
692bool SmartThumbnail::isFullyLoaded() const noexcept
694 return thumbnail->isFullyLoaded();
697juce::int64 SmartThumbnail::getNumSamplesFinished() const noexcept
699 return thumbnail->getNumSamplesFinished();
702float SmartThumbnail::getApproximatePeak()
const
704 return thumbnail->getApproximatePeak();
707void SmartThumbnail::getApproximateMinMax (
double startTime,
double endTime,
int channelIndex,
708 float& minValue,
float& maxValue)
const noexcept
710 thumbnail->getApproximateMinMax (startTime, endTime, channelIndex,
716 return thumbnail->getHashCode();
719void SmartThumbnail::reset (
int numChannels,
double sampleRate,
juce::int64 totalSamplesInSource)
721 thumbnail->
reset (numChannels, sampleRate, totalSamplesInSource);
725 int startOffsetInBuffer,
int numSamples)
727 thumbnail->addBlock (sampleNumberInSource, buffer,
728 startOffsetInBuffer, numSamples);
732void SmartThumbnail::timerCallback()
736 auto& afm = engine.getAudioFileManager();
737 auto& proxyGen = afm.proxyGenerator;
742 const bool isGeneratingNow = proxyGen.isProxyBeingGenerated (file);
744 if (wasGeneratingProxy != isGeneratingNow || (thumbnailIsInvalid && file.getFile().exists()))
746 wasGeneratingProxy = isGeneratingNow;
748 if (! isGeneratingNow)
750 afm.checkFileForChanges (file);
751 createThumbnailReader();
755 thumbnailIsInvalid =
true;
761 if (isGeneratingNow || ! isFullyLoaded())
763 float progress = isGeneratingNow ? proxyGen.getProportionComplete (file)
766 if (lastProgress != progress)
768 lastProgress = progress;
772 else if (! thumbnailIsInvalid || ! file.getFile().exists())
780AudioFileManager::AudioFileManager (
Engine& e)
781 : engine (e), cache (e), thumbnailCache (
std::
make_unique<TracktionThumbnailCache> (e))
785AudioFileManager::~AudioFileManager()
790AudioFileManager::KnownFile& AudioFileManager::findOrCreateKnown (
const AudioFile& f)
792 auto hash = f.getHash();
793 auto kf = knownFiles.find (hash);
795 if (kf != knownFiles.end())
796 return *kf->second.get();
799 return *knownFiles[
hash].get();
802void AudioFileManager::clearFiles()
809void AudioFileManager::removeFile (HashCode hash)
813 auto f = knownFiles.find (hash);
815 if (f != knownFiles.end())
816 knownFiles.erase (f);
819AudioFile AudioFileManager::getAudioFile (ProjectItemID sourceID)
821 return AudioFile (engine, engine.getProjectManager().findSourceFile (sourceID));
824AudioFileInfo AudioFileManager::getInfo (
const AudioFile& file)
827 return findOrCreateKnown (file).info;
830bool AudioFileManager::checkFileTime (KnownFile& f)
832 if (! f.info.wasParsedOk
833 || f.info.fileModificationTime != f.file.getFile().getLastModificationTime())
835 f.info = AudioFileInfo::parse (f.file);
842void AudioFileManager::checkFileForChanges (
const AudioFile& file)
846 bool changed =
false;
851 auto f = knownFiles.find (file.getHash());
853 if (f != knownFiles.end())
854 changed = checkFileTime (*f->second);
860 callListenersOnMessageThread (file);
864void AudioFileManager::checkFilesForChanges()
866 TRACKTION_ASSERT_MESSAGE_THREAD
873 for (
auto& f : knownFiles)
874 if (checkFileTime (*f.second))
875 changedFiles.add (f.second->file);
878 for (
auto& f : changedFiles)
885void AudioFileManager::releaseAllFiles()
887 cache.releaseAllFiles();
891 for (
auto t : activeThumbnails)
895void AudioFileManager::releaseFile (
const AudioFile& file)
897 cache.releaseFile (file);
901 for (
auto t : activeThumbnails)
906void AudioFileManager::callListeners (
const AudioFile& file)
909 TRACKTION_ASSERT_MESSAGE_THREAD
911 for (
auto h : thumbnailTypeHashes)
913 const auto hashCode =
hash ((
size_t) file.getHash(), h);
914 thumbnailCache->removeThumb (
static_cast<juce::int64> (hashCode));
919 for (
auto t : activeThumbnails)
921 t->audioFileChanged();
924void AudioFileManager::callListenersOnMessageThread (
const AudioFile& file)
926 if (juce::MessageManager::existsAndIsCurrentThread())
927 callListeners (file);
932 eng->getAudioFileManager().callListeners (file);
936void AudioFileManager::forceFileUpdate (
const AudioFile& file)
939 TRACKTION_ASSERT_MESSAGE_THREAD
944 auto f = knownFiles.find (file.getHash());
946 if (f != knownFiles.end())
948 f->second->info = AudioFileInfo::parse (f->second->file);
950 callListeners (file);
954void AudioFileManager::validateFile (
const AudioFile& file,
bool updateInfo)
959 forceFileUpdate (file);
962 cache.validateFile (file);
965void AudioFileManager::checkFileForChangesAsync (
const AudioFile& file)
968 filesToCheck.addIfNotAlreadyThere (file);
972void AudioFileManager::handleAsyncUpdate()
975 AudioFile fileToCheck (engine);
979 fileToCheck = filesToCheck.getUnchecked (filesToCheck.size() - 1);
980 filesToCheck.removeLast();
982 if (filesToCheck.size() > 0)
986 if (! fileToCheck.isNull())
987 checkFileForChanges (fileToCheck);
void triggerAsyncUpdate()
int getNumSamples() const noexcept
virtual bool loadFrom(InputStream &input)=0
virtual void saveTo(OutputStream &output) const=0
bool openedOk() const noexcept
File getChildFile(StringRef relativeOrAbsolutePath) 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.
~SmartThumbnail() override
Destructor.
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...
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
size_t hash(size_t seed, const T &v)
Hashes a type with a given seed and returns the new hash value.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.