diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 9ff3dfcf..17e34f89 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -31,26 +31,25 @@ #include #include #include -#include using namespace std::chrono_literals; template -std::unique_ptr> readFromFile(SndfileHandle& sndFile, int numFrames) +std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32_t numFrames) { auto returnedBuffer = std::make_unique>(sndFile.channels(), numFrames); if (sndFile.channels() == 1) { sndFile.readf(returnedBuffer->channelWriter(0), numFrames); } else if (sndFile.channels() == 2) { - auto tempReadBuffer = std::make_unique>(1, 2 * numFrames); + auto tempReadBuffer = std::make_unique>(1, 2 * numFrames); sndFile.readf(tempReadBuffer->channelWriter(0), numFrames); - sfz::readInterleaved(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1)); + sfz::readInterleaved(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1)); } return returnedBuffer; } -absl::optional sfz::FilePool::getFileInformation(const std::string& filename, uint32_t offset) noexcept +absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept { - fs::path file { rootDirectory / filename }; + fs::path file{ rootDirectory / filename }; if (!fs::exists(file)) return {}; @@ -63,6 +62,7 @@ absl::optional sfz::FilePool::getFileInformation FileInformation returnedValue; returnedValue.end = static_cast(sndFile.frames()); returnedValue.sampleRate = static_cast(sndFile.samplerate()); + returnedValue.numChannels = sndFile.channels(); SF_INSTRUMENT instrumentInfo; sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); @@ -71,113 +71,111 @@ absl::optional sfz::FilePool::getFileInformation returnedValue.loopEnd = instrumentInfo.loops[0].end; } - // FIXME: Large offsets will require large preloading; is this OK in practice? - const auto preloadedSize = [&]() { - if (config::preloadSize == 0) - return returnedValue.end; - else - return std::min(returnedValue.end, offset + static_cast(config::preloadSize)); - }(); - - if (preloadedData.contains(filename)) { - auto alreadyPreloaded = preloadedData[filename]; - if (preloadedSize > alreadyPreloaded->getNumFrames()) { - // FIXME: Okay, ideally here you would have a double indirection so that we can update _all_ the preloaded - // files in previous regions to account for the new offset - // - // Before this next command, some old regions and the file pool hold a shared pointer to the same audio buffer. - // This audio buffer is OK for the old regions, but too small for the new one. - // By resetting the filepool data to a new, longer audiobuffer, we are creating 2 copies of the same audio data. - // The filepool and the new regions have the longer copy, and the older regions have the shorter copy. - // This is not entirely optimal, but is it better to write a double shared pointer ? - // std::shared_ptr>> is a bit ugly... - alreadyPreloaded.reset(readFromFile(sndFile, preloadedSize).release()); - } - returnedValue.preloadedData = alreadyPreloaded; - } else { - returnedValue.preloadedData = readFromFile(sndFile, preloadedSize); - preloadedData[filename] = returnedValue.preloadedData; - } - return returnedValue; } -void sfz::FilePool::enqueueLoading(Voice* voice, const std::string* sample, int numFrames, unsigned ticket) noexcept +bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept { - if (!loadingQueue.try_enqueue({ voice, sample, numFrames, ticket })) { - DBG("Problem enqueuing a file read for file " << sample); + fs::path file{ rootDirectory / filename }; + if (!fs::exists(file)) + return false; + + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + if (sndFile.channels() != 1 && sndFile.channels() != 2) + return false; + + // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same + const uint32_t frames = sndFile.frames(); + const auto framesToLoad = [&]() { + if (preloadSize == 0) + return frames; + else + return min(frames, maxOffset + preloadSize); + }(); + + if (preloadedFiles.contains(filename)) { + if (framesToLoad > preloadedFiles[filename].preloadedData->getNumFrames()) { + preloadedFiles[filename].preloadedData = readFromFile(sndFile, framesToLoad); + } + } else { + preloadedFiles.insert_or_assign(filename, { readFromFile(sndFile, framesToLoad), static_cast(sndFile.samplerate()) * static_cast(oversamplingFactor) }); } + + return true; +} + +sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept +{ + auto promise = std::make_shared(); + const auto preloaded = preloadedFiles.find(filename); + if (preloaded != preloadedFiles.end()) { + promise->filename = preloaded->first; + promise->preloadedData = preloaded->second.preloadedData; + promise->sampleRate = preloaded->second.sampleRate; + promise->oversamplingFactor = oversamplingFactor; + temporaryFilePromises.push_back(promise); + } + return promise; +} + +void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept +{ + // Update all the preloaded sizes + for (auto& preloadedFile: preloadedFiles) { + const auto numFrames = preloadedFile.second.preloadedData->getNumFrames(); + const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0; + fs::path file{ rootDirectory / std::string(preloadedFile.first) }; + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset); + } + this->preloadSize = preloadSize; } void sfz::FilePool::loadingThread() noexcept { while (!quitThread) { - if (emptyQueue) { - while(loadingQueue.pop()) {} - emptyQueue = false; - continue; - } + std::this_thread::sleep_for(1ms); + for (auto& promise : temporaryFilePromises) { + if (!promise->dataReady) { + fs::path file{ rootDirectory / std::string(promise->filename) }; + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + if (sndFile.error() != 0) + continue; - FileLoadingInformation fileToLoad {}; - if (!loadingQueue.wait_dequeue_timed(fileToLoad, 200ms)) { - continue; - } - - if (fileToLoad.voice == nullptr) { - DBG("Background thread error: voice is null."); - continue; - } - - if (fileToLoad.sample == nullptr) { - DBG("Background thread error: sample is null."); - continue; - } - - DBG("Background loading of: " << *fileToLoad.sample); - fs::path file { rootDirectory / *fileToLoad.sample }; - if (!fs::exists(file)) { - DBG("Background thread: no file " << *fileToLoad.sample << " exists."); - continue; - } - - SndfileHandle sndFile(reinterpret_cast(file.c_str())); - - std::lock_guard guard { fileHandleMutex }; - fileHandles.emplace_back(readFromFile(sndFile, fileToLoad.numFrames)); - fileToLoad.voice->setFileData(fileHandles.back(), fileToLoad.ticket); - } -} - -void sfz::FilePool::garbageThread() noexcept -{ - while (!quitThread) { - fileHandleMutex.lock(); - for (auto handle = fileHandles.begin(); handle < fileHandles.end();) { - if (handle->use_count() == 1) { - handle->reset(); - std::iter_swap(handle, fileHandles.end() - 1); - fileHandles.pop_back(); - } else { - handle++; + DBG("Loading file for " << *promise->filename << " in the background"); + const uint32_t frames = sndFile.frames(); + promise->fileData = readFromFile(sndFile, frames); + promise->dataReady = true; } } - fileHandleMutex.unlock(); - std::this_thread::sleep_for(200ms); + + promisesToClean.clear(); } } void sfz::FilePool::clear() { - preloadedData.clear(); - fileHandles.clear(); - while (loadingQueue.pop()) { - // Pop the queue - } + preloadedFiles.clear(); + temporaryFilePromises.clear(); + promisesToClean.clear(); } -void sfz::FilePool::emptyFileLoadingQueue() noexcept +void sfz::FilePool::cleanupPromises() noexcept { - emptyQueue = true; - while (emptyQueue) - std::this_thread::sleep_for(1ms); + if (temporaryFilePromises.empty()) + return; + + auto promise = temporaryFilePromises.begin(); + auto sentinel = temporaryFilePromises.end() - 1; + while (promise != temporaryFilePromises.end()) { + if (promise->use_count() == 1) { + promisesToClean.push_back(*promise); + std::iter_swap(promise, sentinel); + sentinel--; + temporaryFilePromises.pop_back(); + } + else { + promise++; + } + } } diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index c7f63fe3..f5bd445c 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -26,16 +26,36 @@ #include "Defaults.h" #include "LeakDetector.h" #include "AudioBuffer.h" -#include "Voice.h" +#include "SIMDHelpers.h" #include "ghc/fs_std.hpp" #include "moodycamel/readerwriterqueue.h" #include #include #include -#include +#include "absl/strings/string_view.h" #include namespace sfz { +using AudioBufferPtr = std::shared_ptr>; + + +struct PreloadedFileHandle +{ + std::shared_ptr> preloadedData {}; + float sampleRate { config::defaultSampleRate }; +}; + +struct FilePromise +{ + absl::string_view filename {}; + AudioBufferPtr preloadedData {}; + std::unique_ptr> fileData {}; + float sampleRate { config::defaultSampleRate }; + std::atomic dataReady { false }; + Oversampling oversamplingFactor { config::defaultOversamplingFactor }; +}; + +using FilePromisePtr = std::shared_ptr; /** * @brief This is a singleton-designed class that holds all the preloaded * data as well as functions to request new file data and collect the file @@ -53,6 +73,8 @@ namespace sfz { * to 1. A garbage collection thread then runs regularly to clear the memory of all file * handles with a reference count of 1. */ + + class FilePool { public: FilePool() { } @@ -61,7 +83,6 @@ public: { quitThread = true; fileLoadingThread.join(); - garbageCollectionThread.join(); } /** * @brief Set the root directory from which to search for files to load @@ -74,65 +95,50 @@ public: * * @return size_t */ - size_t getNumPreloadedSamples() const noexcept { return preloadedData.size(); } + size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); } struct FileInformation { uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopEnd { Default::loopRange.getEnd() }; double sampleRate { config::defaultSampleRate }; - std::shared_ptr> preloadedData; + int numChannels { 0 }; }; + /** - * @brief Get metadata information about a file as well as the first chunk of data + * @brief Get metadata information about a file. * - * If the same file was already preloaded and with a compatible offset, the handle - * is shared between the regions. Otherwise, a new handle is created (the others keep - * the old preloaded file). + * @param filename + * @return absl::optional + */ + absl::optional getFileInformation(const std::string& filename) noexcept; + + /** + * @brief Check that a file is preloaded with the proper offset bounds * * @param filename * @param offset the maximum offset to consider for preloading. The total preloaded - * size will be preloadedSize + offset - * @return absl::optional + * size will be preloadSize + offset + * @return true if the preloading went fine + * @return false if something went wrong () */ - absl::optional getFileInformation(const std::string& filename, uint32_t offset) noexcept; - /** - * @brief Queue a full loading operation for a given voice. - * - * The goal of the ticket is to avoid file loading operations that for some reason - * finish too late a "replace" a proper sample with an obsolete one for a voice. - * - * @param voice the voice to give the full file data to - * @param sample the sample file - * @param numFrames the number of frames to load from the file - * @param ticket an ideally unique ticket number for this file. - */ - void enqueueLoading(Voice* voice, const std::string* sample, int numFrames, unsigned ticket) noexcept; + bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; + /** * @brief Clear all preloaded files. * */ void clear(); - /** - * @brief Empty the file loading queue. This function will lock and wait - * for the background thread to finish its business, so don't call it from - * the audio thread. - * - */ - void emptyFileLoadingQueue() noexcept; + + void cleanupPromises() noexcept; + FilePromisePtr getFilePromise(const std::string& filename) noexcept; + void setPreloadSize(uint32_t preloadSize) noexcept; private: fs::path rootDirectory; - struct FileLoadingInformation { - Voice* voice; - const std::string* sample; - int numFrames; - unsigned ticket; - }; - moodycamel::BlockingReaderWriterQueue loadingQueue { config::numVoices }; void loadingThread() noexcept; - void garbageThread() noexcept; + uint32_t preloadSize { config::preloadSize }; Oversampling oversamplingFactor { config::defaultOversamplingFactor }; // Signals @@ -140,10 +146,11 @@ private: bool emptyQueue { false }; std::mutex fileHandleMutex; - std::vector>> fileHandles; - absl::flat_hash_map>> preloadedData; + + std::vector temporaryFilePromises; + std::vector promisesToClean; + absl::flat_hash_map preloadedFiles; std::thread fileLoadingThread { &FilePool::loadingThread, this }; - std::thread garbageCollectionThread { &FilePool::garbageThread, this }; LEAK_DETECTOR(FilePool); }; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index a5d1dc21..1e7a7758 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -659,22 +659,6 @@ uint32_t sfz::Region::trueSampleEnd() const noexcept return min(sampleEnd, loopRange.getEnd()); } -bool sfz::Region::canUsePreloadedData() const noexcept -{ - if (preloadedData == nullptr) - return false; - - return trueSampleEnd() < static_cast(preloadedData->getNumFrames()); -} - -bool sfz::Region::isStereo() const noexcept -{ - if (isGenerator()) - return 1; - - return (this->preloadedData->getNumChannels() == 2); -} - template float crossfadeIn(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) { diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index b75b93e4..77c2a701 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -149,13 +149,7 @@ struct Region { * @return false */ void registerTempo(float secondsPerQuarter) noexcept; - /** - * @brief Is the underlying region sample a stereo one? - * - * @return true - * @return false - */ - bool isStereo() const noexcept; + /** * @brief Get the base pitch of the region depending on which note has been * pressed and at which velocity. @@ -339,8 +333,7 @@ struct Region { EGDescription pitchEG; EGDescription filterEG; - double sampleRate { config::defaultSampleRate }; - std::shared_ptr> preloadedData { nullptr }; + bool isStereo { false }; private: const MidiState& midiState; bool keySwitched { true }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7b2a7b7f..8c06283e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -227,7 +227,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) auto region = currentRegion->get(); if (!region->isGenerator()) { - auto fileInformation = resources.filePool.getFileInformation(region->sample, region->offset + region->offsetRandom); + auto fileInformation = resources.filePool.getFileInformation(region->sample); if (!fileInformation) { DBG("Removing the region with sample " << region->sample); std::iter_swap(currentRegion, lastRegion); @@ -236,8 +236,12 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) } region->sampleEnd = std::min(region->sampleEnd, fileInformation->end); region->loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd); - region->preloadedData = fileInformation->preloadedData; - region->sampleRate = fileInformation->sampleRate; + if (fileInformation->numChannels == 2) + region->isStereo = true; + + // TODO: adjust with LFO targets + const auto maxOffset { region->offset + region->offsetRandom }; + resources.filePool.preloadFile(region->sample, maxOffset); } for (auto note = 0; note < 128; note++) { @@ -313,7 +317,7 @@ int sfz::Synth::getNumActiveVoices() const noexcept void sfz::Synth::garbageCollect() noexcept { for (auto& voice : voices) { - voice->garbageCollect(); + } } @@ -348,6 +352,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept ScopedFTZ ftz; buffer.fill(0.0f); + resources.filePool.cleanupPromises(); + AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) return; @@ -387,10 +393,6 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity continue; voice->startVoice(region, delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - resources.filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -420,10 +422,6 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit continue; voice->startVoice(region, delay, channel, noteNumber, replacedVelocity, Voice::TriggerType::NoteOff); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - resources.filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -449,10 +447,6 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc continue; voice->startVoice(region, delay, channel, ccNumber, ccValue, Voice::TriggerType::CC); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - resources.filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -533,10 +527,9 @@ void sfz::Synth::resetVoices(int numVoices) std::this_thread::sleep_for(1ms); } - resources.filePool.emptyFileLoadingQueue(); voices.clear(); for (int i = 0; i < numVoices; ++i) - voices.push_back(std::make_unique(midiState)); + voices.push_back(std::make_unique(midiState, resources)); for (auto& voice: voices) { voice->setSampleRate(this->sampleRate); @@ -554,7 +547,6 @@ void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept std::this_thread::sleep_for(1ms); } - resources.filePool.emptyFileLoadingQueue(); for (auto& voice: voices) voice->reset(); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 2cfa3614..775963c7 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -24,6 +24,7 @@ #pragma once #include "Resources.h" #include "Parser.h" +#include "Voice.h" #include "Region.h" #include "LeakDetector.h" #include "MidiState.h" @@ -353,11 +354,6 @@ private: std::vector masterOpcodes; std::vector groupOpcodes; - // Singletons passed as references to the voices - // TODO: these should probably go in a global singleton holder along with a buffer distribution and LFO/EG stuff... - Resources resources; - MidiState midiState; - /** * @brief Find a voice that is not currently playing * @@ -396,6 +392,10 @@ private: std::atomic canEnterCallback { true }; std::atomic inCallback { false }; + // Singletons passed as references to the voices + Resources resources; + MidiState midiState; + LEAK_DETECTOR(Synth); }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index cf351ca3..58a5700e 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -31,8 +31,8 @@ #include "absl/algorithm/container.h" #include -sfz::Voice::Voice(const MidiState& midiState) - : midiState(midiState) +sfz::Voice::Voice(const sfz::MidiState& midiState, sfz::Resources& resources) + : midiState(midiState), resources(resources) { } @@ -44,6 +44,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, triggerValue = value; this->region = region; + state = State::playing; ASSERT(delay >= 0); if (delay < 0) @@ -51,8 +52,16 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, // DBG("Starting voice with " << region->sample); - state = State::playing; - speedRatio = static_cast(region->sampleRate / this->sampleRate); + if (!region->isGenerator()) { + currentPromise = resources.filePool.getFilePromise(region->sample); + if (currentPromise == nullptr) { + DBG("[Voice] Could not fetch the file promise for sample " << region->sample); + reset(); + return; + } + speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); + } + pitchRatio = region->getBasePitchVariation(number, value); baseVolumedB = region->getBaseVolumedB(number); @@ -118,15 +127,6 @@ void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept normalizePercents(region->amplitudeEG.getStart(midiState.cc, velocity))); } -void sfz::Voice::setFileData(std::shared_ptr> file, unsigned ticket) noexcept -{ - if (ticket != this->ticket) - return; - - fileData = std::move(file); - dataReady.store(true); -} - bool sfz::Voice::isFree() const noexcept { return (region == nullptr); @@ -245,7 +245,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept else fillWithData(delayed_buffer); - if (region->isStereo()) + if (region->isStereo) processStereo(buffer); else processMono(buffer); @@ -352,12 +352,13 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept return; auto source { [&]() { - if (region->canUsePreloadedData()) - return AudioSpan(*region->preloadedData); - else if (!dataReady) - return AudioSpan(*region->preloadedData); + // if (region->trueSampleEnd() < currentPromise->preloadedData->getNumFrames()) + // return AudioSpan(*currentPromise->preloadedData); + // else + if (!currentPromise->dataReady) + return AudioSpan(*currentPromise->preloadedData); else - return AudioSpan(*fileData); + return AudioSpan(*currentPromise->fileData); }() }; auto indices = indexSpan.first(buffer.getNumFrames()); @@ -483,30 +484,17 @@ sfz::Voice::TriggerType sfz::Voice::getTriggerType() const noexcept void sfz::Voice::reset() noexcept { - dataReady.store(false); - fileData.reset(); state = State::idle; if (region != nullptr) { DBG("Reset voice with sample " << region->sample); } region = nullptr; + currentPromise.reset(); sourcePosition = 0; floatPositionOffset = 0.0f; noteIsOff = false; } -void sfz::Voice::garbageCollect() noexcept -{ - if (state == State::idle && region == nullptr) { - fileData.reset(); - } -} - -void sfz::Voice::expectFileData(unsigned ticket) -{ - this->ticket = ticket; -} - float sfz::Voice::getMeanSquaredAverage() const noexcept { return powerHistory.getAverage(); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 350265c2..d90e0f7e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -29,6 +29,7 @@ #include "Region.h" #include "AudioBuffer.h" #include "MidiState.h" +#include "Resources.h" #include "AudioSpan.h" #include "LeakDetector.h" #include @@ -50,7 +51,7 @@ public: * * @param midiState */ - Voice(const MidiState& midiState); + Voice(const MidiState& midiState, Resources& resources); enum class TriggerType { NoteOn, NoteOff, @@ -98,23 +99,6 @@ public: */ void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType) noexcept; - /** - * @brief Tells the voice that it should expect to receive a file at some point using the - * setFileData() function. The ticket is a unique identifier that will prevent the file data - * to be set "too late"; if the voice receives the file for an older ticket, it will discard - * it. - * - * @param ticket - */ - void expectFileData(unsigned ticket); - /** - * @brief Sets the file data for a given ticket. The voice can freely release and destroy the - * shared pointer, as it will be garbage collected by the file pool afterwards. - * - * @param file - * @param ticket - */ - void setFileData(std::shared_ptr> file, unsigned ticket) noexcept; /** * @brief Register a note-off event; this may trigger a release. * @@ -221,11 +205,6 @@ public: * */ void reset() noexcept; - /** - * @brief Clear the loaded file data if it's not useful anymore - * - */ - void garbageCollect() noexcept; /** * @brief Get the mean squared power of the last rendered block. This is used @@ -309,9 +288,7 @@ private: int sourcePosition { 0 }; int initialDelay { 0 }; - std::atomic dataReady { false }; - std::shared_ptr> fileData { nullptr }; - unsigned ticket { 0 }; + FilePromisePtr currentPromise { nullptr }; Buffer tempBuffer1; Buffer tempBuffer2; @@ -326,6 +303,8 @@ private: float sampleRate { config::defaultSampleRate }; const MidiState& midiState; + Resources& resources; + ADSREnvelope egEnvelope; LinearEnvelope volumeEnvelope; // dB events but the envelope output is linear gain LinearEnvelope amplitudeEnvelope; // linear events diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 3dd5a1cd..20e5ab4b 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -257,9 +257,9 @@ TEST_CASE("[Files] Channels (channels.sfz)") synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels.sfz"); REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getRegionView(0)->sample == "mono_sample.wav"); - REQUIRE(!synth.getRegionView(0)->isStereo()); + REQUIRE(!synth.getRegionView(0)->isStereo); REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav"); - REQUIRE(synth.getRegionView(1)->isStereo()); + REQUIRE(synth.getRegionView(1)->isStereo); } TEST_CASE("[Files] sw_default") @@ -315,4 +315,4 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(2)->amplitudeCC ); REQUIRE( synth.getRegionView(2)->amplitudeCC->first == 10 ); REQUIRE( synth.getRegionView(2)->amplitudeCC->second == 34.0f ); -} \ No newline at end of file +}