Changed the logic for the file loading

This commit is contained in:
Paul Ferrand 2019-12-01 20:18:59 +01:00
parent 1536ead90b
commit 630220e7e0
9 changed files with 188 additions and 247 deletions

View file

@ -31,26 +31,25 @@
#include <mutex>
#include <sndfile.hh>
#include <thread>
#include <mutex>
using namespace std::chrono_literals;
template <class T>
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, int numFrames)
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames)
{
auto returnedBuffer = std::make_unique<sfz::AudioBuffer<T>>(sndFile.channels(), numFrames);
if (sndFile.channels() == 1) {
sndFile.readf(returnedBuffer->channelWriter(0), numFrames);
} else if (sndFile.channels() == 2) {
auto tempReadBuffer = std::make_unique<sfz::AudioBuffer<float>>(1, 2 * numFrames);
auto tempReadBuffer = std::make_unique<sfz::AudioBuffer<T>>(1, 2 * numFrames);
sndFile.readf(tempReadBuffer->channelWriter(0), numFrames);
sfz::readInterleaved<float>(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1));
sfz::readInterleaved<T>(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1));
}
return returnedBuffer;
}
absl::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename, uint32_t offset) noexcept
absl::optional<sfz::FilePool::FileInformation> 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::FileInformation> sfz::FilePool::getFileInformation
FileInformation returnedValue;
returnedValue.end = static_cast<uint32_t>(sndFile.frames());
returnedValue.sampleRate = static_cast<double>(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::FileInformation> 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<uint32_t>(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<std::shared_ptr<sfz::AudioBuffer>>> is a bit ugly...
alreadyPreloaded.reset(readFromFile<float>(sndFile, preloadedSize).release());
}
returnedValue.preloadedData = alreadyPreloaded;
} else {
returnedValue.preloadedData = readFromFile<float>(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<const char*>(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<float>(sndFile, framesToLoad);
}
} else {
preloadedFiles.insert_or_assign(filename, { readFromFile<float>(sndFile, framesToLoad), static_cast<float>(sndFile.samplerate()) * static_cast<float>(oversamplingFactor) });
}
return true;
}
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
{
auto promise = std::make_shared<FilePromise>();
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<const char*>(file.c_str()));
preloadedFile.second.preloadedData = readFromFile<float>(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<const char*>(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<const char*>(file.c_str()));
std::lock_guard<std::mutex> guard { fileHandleMutex };
fileHandles.emplace_back(readFromFile<float>(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<float>(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++;
}
}
}

View file

@ -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 <absl/container/flat_hash_map.h>
#include <mutex>
#include <absl/types/optional.h>
#include <string_view>
#include "absl/strings/string_view.h"
#include <thread>
namespace sfz {
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
struct PreloadedFileHandle
{
std::shared_ptr<AudioBuffer<float>> preloadedData {};
float sampleRate { config::defaultSampleRate };
};
struct FilePromise
{
absl::string_view filename {};
AudioBufferPtr preloadedData {};
std::unique_ptr<AudioBuffer<float>> fileData {};
float sampleRate { config::defaultSampleRate };
std::atomic<bool> dataReady { false };
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
};
using FilePromisePtr = std::shared_ptr<FilePromise>;
/**
* @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<AudioBuffer<float>> 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<FileInformation>
*/
absl::optional<FileInformation> 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<FileInformation>
* size will be preloadSize + offset
* @return true if the preloading went fine
* @return false if something went wrong ()
*/
absl::optional<FileInformation> 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<FileLoadingInformation> 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<std::shared_ptr<AudioBuffer<float>>> fileHandles;
absl::flat_hash_map<absl::string_view, std::shared_ptr<AudioBuffer<float>>> preloadedData;
std::vector<FilePromisePtr> temporaryFilePromises;
std::vector<FilePromisePtr> promisesToClean;
absl::flat_hash_map<absl::string_view, PreloadedFileHandle> preloadedFiles;
std::thread fileLoadingThread { &FilePool::loadingThread, this };
std::thread garbageCollectionThread { &FilePool::garbageThread, this };
LEAK_DETECTOR(FilePool);
};
}

View file

@ -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<uint32_t>(preloadedData->getNumFrames());
}
bool sfz::Region::isStereo() const noexcept
{
if (isGenerator())
return 1;
return (this->preloadedData->getNumChannels() == 2);
}
template<class T, class U>
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
{

View file

@ -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<AudioBuffer<float>> preloadedData { nullptr };
bool isStereo { false };
private:
const MidiState& midiState;
bool keySwitched { true };

View file

@ -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<float> 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, &region->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, &region->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, &region->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<Voice>(midiState));
voices.push_back(std::make_unique<Voice>(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();

View file

@ -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<Opcode> masterOpcodes;
std::vector<Opcode> 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<bool> canEnterCallback { true };
std::atomic<bool> inCallback { false };
// Singletons passed as references to the voices
Resources resources;
MidiState midiState;
LEAK_DETECTOR(Synth);
};

View file

@ -31,8 +31,8 @@
#include "absl/algorithm/container.h"
#include <memory>
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<float>(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<float>(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<AudioBuffer<float>> 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<float> 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<float> buffer) noexcept
return;
auto source { [&]() {
if (region->canUsePreloadedData())
return AudioSpan<const float>(*region->preloadedData);
else if (!dataReady)
return AudioSpan<const float>(*region->preloadedData);
// if (region->trueSampleEnd() < currentPromise->preloadedData->getNumFrames())
// return AudioSpan<const float>(*currentPromise->preloadedData);
// else
if (!currentPromise->dataReady)
return AudioSpan<const float>(*currentPromise->preloadedData);
else
return AudioSpan<const float>(*fileData);
return AudioSpan<const float>(*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();

View file

@ -29,6 +29,7 @@
#include "Region.h"
#include "AudioBuffer.h"
#include "MidiState.h"
#include "Resources.h"
#include "AudioSpan.h"
#include "LeakDetector.h"
#include <absl/types/span.h>
@ -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<AudioBuffer<float>> 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<bool> dataReady { false };
std::shared_ptr<AudioBuffer<float>> fileData { nullptr };
unsigned ticket { 0 };
FilePromisePtr currentPromise { nullptr };
Buffer<float> tempBuffer1;
Buffer<float> tempBuffer2;
@ -326,6 +303,8 @@ private:
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
Resources& resources;
ADSREnvelope<float> egEnvelope;
LinearEnvelope<float> volumeEnvelope; // dB events but the envelope output is linear gain
LinearEnvelope<float> amplitudeEnvelope; // linear events

View file

@ -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 );
}
}