From 5f564d749a14c655ef75e12fea025bec8e2aed49 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 16 Mar 2020 23:30:23 +0100 Subject: [PATCH 01/16] Add oscillator=on and wavetables from file --- src/sfizz/FilePool.cpp | 7 ++-- src/sfizz/FilePool.h | 18 ++++++-- src/sfizz/Region.cpp | 3 ++ src/sfizz/Region.h | 1 + src/sfizz/Synth.cpp | 6 ++- src/sfizz/Voice.cpp | 22 ++++++---- src/sfizz/Wavetables.cpp | 88 +++++++++++++++++++++++++++++++++++++++- src/sfizz/Wavetables.h | 12 +++++- 8 files changed, 140 insertions(+), 17 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index d5471a12..59a2047a 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -274,7 +274,7 @@ void sfz::FilePool::tryToClearPromises() std::this_thread::sleep_for(std::chrono::milliseconds(1)); for (auto& promise: promisesToClear) { - if (promise->dataReady) + if (promise->dataStatus != FilePromise::DataStatus::Wait) promise->reset(); } } @@ -313,11 +313,12 @@ void sfz::FilePool::loadingThread() noexcept SndfileHandle sndFile(file.string().c_str()); if (sndFile.error() != 0) { DBG("[sfizz] libsndfile errored for " << promise->filename << " with message " << sndFile.strError()); + promise->dataStatus = FilePromise::DataStatus::Error; continue; } const auto frames = static_cast(sndFile.frames()); streamFromFile(sndFile, frames, oversamplingFactor, promise->fileData, &promise->availableFrames); - promise->dataReady = true; + promise->dataStatus = FilePromise::DataStatus::Ready; const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime; logger.logFileTime(waitDuration, loadDuration, frames, promise->filename); @@ -352,7 +353,7 @@ void sfz::FilePool::cleanupPromises() noexcept auto clearedIterator = promisesToClear.begin(); auto clearedSentinel = promisesToClear.rbegin(); while (clearedIterator < clearedSentinel.base()) { - if (clearedIterator->get()->dataReady == false) { + if (clearedIterator->get()->dataStatus == FilePromise::DataStatus::Wait) { emptyPromises.push_back(*clearedIterator); std::iter_swap(clearedIterator, clearedSentinel); ++clearedSentinel; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index c75e6c75..dfebd5b1 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -55,7 +55,7 @@ struct FilePromise { AudioSpan getData() { - if (dataReady) + if (dataStatus == DataStatus::Ready) return AudioSpan(fileData); else if (availableFrames > preloadedData->getNumFrames()) return AudioSpan(fileData).first(availableFrames); @@ -69,18 +69,30 @@ struct FilePromise preloadedData.reset(); filename = ""; availableFrames = 0; - dataReady = false; + dataStatus = DataStatus::Wait; oversamplingFactor = config::defaultOversamplingFactor; sampleRate = config::defaultSampleRate; } + void waitCompletion() + { + while (dataStatus == DataStatus::Wait) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + enum class DataStatus { + Wait = 0, + Ready, + Error, + }; + absl::string_view filename {}; AudioBufferPtr preloadedData {}; AudioBuffer fileData {}; float sampleRate { config::defaultSampleRate }; Oversampling oversamplingFactor { config::defaultOversamplingFactor }; std::atomic availableFrames { 0 }; - std::atomic dataReady { false }; + std::atomic dataStatus { DataStatus::Wait }; std::chrono::time_point creationTime; LEAK_DETECTOR(FilePromise); diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 8d8f14ee..4f05f314 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -98,6 +98,9 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) // Wavetable oscillator case hash("oscillator_phase"): setValueFromOpcode(opcode, oscillatorPhase, Default::oscillatorPhaseRange); + case hash("oscillator"): + if (auto value = readBooleanFromOpcode(opcode)) + oscillator = *value; break; // Instrument settings: voice lifecycle diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 3ef284d0..ebacc207 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -228,6 +228,7 @@ struct Region { // Wavetable oscillator float oscillatorPhase { Default::oscillatorPhase }; + bool oscillator = false; // Instrument settings: voice lifecycle uint32_t group { Default::group }; // group diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 930e1c9e..e370fc79 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -306,6 +306,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) return false; resources.filePool.setRootDirectory(parser.originalDirectory()); + resources.wavePool.clearFileWaves(); auto currentRegion = regions.begin(); auto lastRegion = regions.rbegin(); @@ -324,7 +325,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) while (currentRegion < lastRegion.base()) { auto region = currentRegion->get(); - if (!region->isGenerator()) { + if (!region->oscillator && !region->isGenerator()) { if (!resources.filePool.checkSample(region->sample)) { removeCurrentRegion(); continue; @@ -360,6 +361,9 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) if (!resources.filePool.preloadFile(region->sample, maxOffset)) removeCurrentRegion(); } + else if (region->oscillator && !region->isGenerator()) { + resources.wavePool.createFileWave(resources.filePool, region->sample); + } for (auto note = 0; note < 128; note++) { if (region->keyRange.containsWithEnd(note) || diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 743c00f7..2b150b42 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -58,7 +58,19 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value break; } waveOscillator.setWavetable(wave); + } else if (region->oscillator) { + const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample); + waveOscillator.setWavetable(wave); + } else { + currentPromise = resources.filePool.getFilePromise(region->sample); + if (currentPromise == nullptr) { + reset(); + return; + } + speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); + } + if (region->oscillator || region->isGenerator()) { float phase; const float phaseParam = region->oscillatorPhase; if (phaseParam >= 0) { @@ -69,14 +81,8 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value phase = phaseDist(Random::randomGenerator); } waveOscillator.setPhase(phase); - } else { - currentPromise = resources.filePool.getFilePromise(region->sample); - if (currentPromise == nullptr) { - reset(); - return; - } - speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); } + pitchRatio = region->getBasePitchVariation(number, value); baseVolumedB = region->getBaseVolumedB(number); @@ -294,7 +300,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept { // Fill buffer with raw data ScopedTiming logger { dataDuration }; - if (region->isGenerator()) + if (region->isGenerator() || region->oscillator) fillWithGenerator(delayed_buffer); else fillWithData(delayed_buffer); diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 355b9fca..9576af8a 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -5,9 +5,9 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Wavetables.h" +#include "FilePool.h" #include "MathHelpers.h" #include -#include namespace sfz { @@ -279,6 +279,31 @@ void WavetableMulti::fillExtra() } } +//------------------------------------------------------------------------------ + +/** + * @brief Harmonic profile which takes its values from a table. + */ +class TabulatedHarmonicProfile : public HarmonicProfile { +public: + explicit TabulatedHarmonicProfile(absl::Span> harmonics) + : _harmonics(harmonics) + { + } + + std::complex getHarmonic(size_t index) const override + { + if (index >= _harmonics.size()) + return {}; + + return _harmonics[index]; + } + +private: + absl::Span> _harmonics; +}; + +//------------------------------------------------------------------------------ WavetablePool::WavetablePool() { @@ -313,4 +338,65 @@ const WavetableMulti* WavetablePool::getWaveSquare() return &wave; } +const WavetableMulti* WavetablePool::getFileWave(const std::string& filename) +{ + auto it = _fileWaves.find(filename); + if (it == _fileWaves.end()) + return nullptr; + + return it->second.get(); +} + +void WavetablePool::clearFileWaves() +{ + _fileWaves.clear(); +} + +const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const std::string& filename) +{ + if (const WavetableMulti* wave = getFileWave(filename)) + return wave; + + if (!filePool.preloadFile(filename, 0)) + return nullptr; + + FilePromisePtr fp = filePool.getFilePromise(filename); + if (!fp) + return nullptr; + + fp->waitCompletion(); + if (fp->dataStatus == FilePromise::DataStatus::Error) + return nullptr; + + // use 1 channel only, maybe warn if file has more channels + auto audioData = fp->fileData.getSpan(0); + size_t fftSize = audioData.size(); + size_t specSize = fftSize / 2 + 1; + + typedef std::complex cpx; + std::unique_ptr spec { new cpx[specSize] }; + + kiss_fftr_cfg cfg = kiss_fftr_alloc(fftSize, false, nullptr, nullptr); + if (!cfg) + throw std::bad_alloc(); + + kiss_fftr(cfg, audioData.data(), reinterpret_cast(spec.get())); + kiss_fftr_free(cfg); + + // scale transform, and normalize amplitude and phase + const std::complex k = std::polar(2.0 / fftSize, -M_PI / 2); + for (size_t i = 0; i < specSize; ++i) + spec[i] *= k; + + TabulatedHarmonicProfile hp { + absl::Span> { spec.get(), specSize } + }; + + auto wave = std::make_shared( + WavetableMulti::createForHarmonicProfile(hp, 1.0)); + + _fileWaves[filename] = wave; + return wave.get(); +} + } // namespace sfz diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index bdc715bc..30640e16 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -9,10 +9,12 @@ #include "LeakDetector.h" #include "Buffer.h" #include +#include #include #include namespace sfz { +class FilePool; class WavetableMulti; @@ -185,15 +187,23 @@ private: }; /** - * @brief Holds predefined wavetables. + * @brief Holds predefined and loaded wavetables. * */ struct WavetablePool { WavetablePool(); + + const WavetableMulti* getFileWave(const std::string& filename); + const WavetableMulti* createFileWave(FilePool& filePool, const std::string& filename); + void clearFileWaves(); + static const WavetableMulti* getWaveSin(); static const WavetableMulti* getWaveTriangle(); static const WavetableMulti* getWaveSaw(); static const WavetableMulti* getWaveSquare(); + +private: + absl::flat_hash_map> _fileWaves; }; } // namespace sfz From e74ab547937a9d682544012c24857d17b387ed53 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 27 Mar 2020 18:47:00 +0100 Subject: [PATCH 02/16] Remove an unneeded header --- src/sfizz/FilePool.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index dfebd5b1..20f73fc8 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -38,7 +38,6 @@ #include "Logger.h" #include #include -#include namespace sfz { using AudioBufferPtr = std::shared_ptr>; From 0de71d8e1bf23702cde09062b14df17e713abefc Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 27 Mar 2020 18:47:19 +0100 Subject: [PATCH 03/16] Add a check that atomic is lock free --- src/sfizz/FilePool.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 59a2047a..69ed4c63 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -90,6 +90,10 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin sfz::FilePool::FilePool(sfz::Logger& logger) : logger(logger) { + FilePromise promise; + if (!promise.dataStatus.is_lock_free()) + DBG("atomic is not lock-free; could cause issues with locking"); + for (int i = 0; i < config::numBackgroundThreads; ++i) threadPool.emplace_back( &FilePool::loadingThread, this ); From 2add056a5173ab2bf92c5b3f3b2b81e3de90ca27 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 27 Mar 2020 19:07:27 +0100 Subject: [PATCH 04/16] Clear wavetables in the clear() method --- src/sfizz/Synth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e370fc79..347e7699 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -143,6 +143,7 @@ void sfz::Synth::clear() effectBuses[0]->setSampleRate(sampleRate); curves = CurveSet::createPredefined(); resources.filePool.clear(); + resources.wavePool.clearFileWaves(); resources.logger.clear(); numGroups = 0; numMasters = 0; @@ -306,7 +307,6 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) return false; resources.filePool.setRootDirectory(parser.originalDirectory()); - resources.wavePool.clearFileWaves(); auto currentRegion = regions.begin(); auto lastRegion = regions.rbegin(); From 62b68b2af3580fca05d6e0ca5afd59d0872e9965 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 19:45:34 +0100 Subject: [PATCH 05/16] Change the FilePool to load files and not only preload --- src/sfizz/FilePool.cpp | 55 ++++++++++++++++++++++++++++++++---------- src/sfizz/FilePool.h | 35 ++++++++++++++++++--------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 69ed4c63..3cd57684 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -170,13 +170,16 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept #endif } -absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept +absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept { fs::path file { rootDirectory / filename }; + if (!fs::exists(file)) + return {}; + SndfileHandle sndFile(file.string().c_str()); if (sndFile.channels() != 1 && sndFile.channels() != 2) { - DBG("Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); + DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); return {}; } @@ -198,13 +201,11 @@ absl::optional sfz::FilePool::getFileInformation bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept { fs::path file { rootDirectory / filename }; - - if (!fs::exists(file)) + auto fileInformation = getFileInformation(filename); + if (!fileInformation) return false; SndfileHandle sndFile(file.string().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 auto frames = static_cast(sndFile.frames()); @@ -215,19 +216,47 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) return min(frames, maxOffset + preloadSize); }(); - if (preloadedFiles.contains(filename)) { - if (framesToLoad > preloadedFiles[filename].preloadedData->getNumFrames()) { + const auto existingFile = preloadedFiles.find(filename); + if (existingFile != preloadedFiles.end()) { + if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { preloadedFiles[filename].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor); } } else { - const float sourceSampleRate { static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()) }; - PreloadedFileHandle handle { readFromFile(sndFile, framesToLoad, oversamplingFactor), sourceSampleRate }; + fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); + FileDataHandle handle { + readFromFile(sndFile, framesToLoad, oversamplingFactor), + *fileInformation + }; preloadedFiles.insert_or_assign(filename, handle); } - return true; } +absl::optional sfz::FilePool::loadFile(const std::string& filename) noexcept +{ + fs::path file { rootDirectory / filename }; + auto fileInformation = getFileInformation(filename); + if (!fileInformation) + return {}; + + SndfileHandle sndFile(file.string().c_str()); + + // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same + const auto frames = static_cast(sndFile.frames()); + const auto existingFile = loadedFiles.find(filename); + if (existingFile != loadedFiles.end()) { + return existingFile->second; + } else { + fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); + FileDataHandle handle { + readFromFile(sndFile, frames, oversamplingFactor), + *fileInformation + }; + loadedFiles.insert_or_assign(filename, handle); + return handle; + } +} + sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept { if (emptyPromises.empty()) { @@ -244,7 +273,7 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n auto promise = emptyPromises.back(); promise->filename = preloaded->first; promise->preloadedData = preloaded->second.preloadedData; - promise->sampleRate = preloaded->second.sampleRate; + promise->sampleRate = preloaded->second.information.sampleRate; promise->oversamplingFactor = oversamplingFactor; promise->creationTime = std::chrono::high_resolution_clock::now(); @@ -396,7 +425,7 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept fs::path file { rootDirectory / std::string(preloadedFile.first) }; SndfileHandle sndFile(file.string().c_str()); preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor); - preloadedFile.second.sampleRate *= samplerateChange; + preloadedFile.second.information.sampleRate *= samplerateChange; } this->oversamplingFactor = factor; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 20f73fc8..450a4c24 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -43,11 +43,19 @@ namespace sfz { using AudioBufferPtr = std::shared_ptr>; +struct FileInformation { + uint32_t end { Default::sampleEndRange.getEnd() }; + uint32_t loopBegin { Default::loopRange.getStart() }; + uint32_t loopEnd { Default::loopRange.getEnd() }; + double sampleRate { config::defaultSampleRate }; + int numChannels { 0 }; +}; + // Strict C++11 disallows member initialization if aggregate initialization is to be used... -struct PreloadedFileHandle +struct FileDataHandle { std::shared_ptr> preloadedData; - float sampleRate; + FileInformation information; }; struct FilePromise @@ -142,14 +150,6 @@ public: */ 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 }; - int numChannels { 0 }; - }; - /** * @brief Get metadata information about a file. * @@ -159,7 +159,7 @@ public: absl::optional getFileInformation(const std::string& filename) noexcept; /** - * @brief Check that a file is preloaded with the proper offset bounds + * @brief Preload a file with the proper offset bounds * * @param filename * @param offset the maximum offset to consider for preloading. The total preloaded @@ -169,6 +169,15 @@ public: */ bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; + /** + * @brief Load a file and return its information. The file pool will store this + * data for future requests so use this function responsibly. + * + * @param filename + * @return A handle on the file data + */ + absl::optional loadFile(const std::string& filename) noexcept; + /** * @brief Check that the sample exists. If not, try to find it in a case insensitive way. * @@ -258,7 +267,9 @@ private: std::atomic addingPromisesToClear { false }; std::atomic canAddPromisesToClear { true }; - absl::flat_hash_map preloadedFiles; + // Preloaded data + absl::flat_hash_map preloadedFiles; + absl::flat_hash_map loadedFiles; std::vector threadPool { }; LEAK_DETECTOR(FilePool); }; From f5ac778c163b1d0ced8c119b9bd02945528aa64d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 19:46:23 +0100 Subject: [PATCH 06/16] Use the new filepool API --- src/sfizz/Wavetables.cpp | 14 +++++--------- src/sfizz/Wavetables.h | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 9576af8a..dd962083 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -357,19 +357,15 @@ const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const st if (const WavetableMulti* wave = getFileWave(filename)) return wave; - if (!filePool.preloadFile(filename, 0)) + auto fileHandle = filePool.loadFile(filename); + if (!fileHandle) return nullptr; - FilePromisePtr fp = filePool.getFilePromise(filename); - if (!fp) - return nullptr; - fp->waitCompletion(); - if (fp->dataStatus == FilePromise::DataStatus::Error) - return nullptr; + if (fileHandle->information.numChannels > 1) + DBG("[sfizz] Only the first channel of " << filename << " will be used to create the wavetable"); - // use 1 channel only, maybe warn if file has more channels - auto audioData = fp->fileData.getSpan(0); + auto audioData = fileHandle->preloadedData->getConstSpan(0); size_t fftSize = audioData.size(); size_t specSize = fftSize / 2 + 1; diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index 30640e16..f3ebd692 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -193,8 +193,27 @@ private: struct WavetablePool { WavetablePool(); + /** + * @brief Get a file wave. Return a silent table if the wave does not exist yet. + * Use createFileWave to preload file waves before calling this function. + * This function is real-time safe. + * + * @param filename the name of the file wave + * @return the wavetable, or a silent table + */ const WavetableMulti* getFileWave(const std::string& filename); + /** + * @brief Load a file wave from the filepool and use it to create a wavetable. + * This function is not real-time safe. + * + * @param filePool the file pool to use to load the file + * @param filename the file name to load + * @return the wavetable + */ const WavetableMulti* createFileWave(FilePool& filePool, const std::string& filename); + /** + * @brief Removes all the stored file waves from the wavetable pool. + */ void clearFileWaves(); static const WavetableMulti* getWaveSin(); From bad1120296be997268e6428b9be4471790c6bc5a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 19:46:42 +0100 Subject: [PATCH 07/16] Pass the samplerate to the createHarmonicProfile function --- src/sfizz/Wavetables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index dd962083..df8a6b17 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -389,7 +389,7 @@ const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const st }; auto wave = std::make_shared( - WavetableMulti::createForHarmonicProfile(hp, 1.0)); + WavetableMulti::createForHarmonicProfile(hp, 1.0, config::tableSize, fileHandle->information.sampleRate)); _fileWaves[filename] = wave; return wave.get(); From b223fb5e4f167e258256741d1efbe80237af90ad Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:11:09 +0100 Subject: [PATCH 08/16] Add a helper to get the normalized phase --- src/sfizz/Region.cpp | 13 +++++++++++++ src/sfizz/Region.h | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 4f05f314..d21b3cc2 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -917,6 +917,19 @@ float sfz::Region::getBaseGain() noexcept return normalizePercents(amplitude); } +float sfz::Region::getPhase() noexcept +{ + float phase; + if (oscillatorPhase >= 0) { + phase = oscillatorPhase * (1.0f / 360.0f); + phase -= static_cast(phase); + } else { + std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; + phase = phaseDist(Random::randomGenerator); + } + return phase; +} + uint32_t sfz::Region::getOffset(Oversampling factor) noexcept { return (offset + offsetDistribution(Random::randomGenerator)) * static_cast(factor); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index ebacc207..b3966a47 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -167,6 +167,12 @@ struct Region { * @return float */ float getBaseGain() noexcept; + /** + * @brief Get the base gain of the region. + * + * @return float + */ + float getPhase() noexcept; /** * @brief Computes the gain value related to the velocity of the note * From dabb7ead9ff8b91e8c50e5df63f705a2fa082c44 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:11:17 +0100 Subject: [PATCH 09/16] Use the phase helper --- src/sfizz/Voice.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2b150b42..e86375f2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -58,9 +58,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value break; } waveOscillator.setWavetable(wave); + waveOscillator.setPhase(region->getPhase()); } else if (region->oscillator) { const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample); waveOscillator.setWavetable(wave); + waveOscillator.setPhase(region->getPhase()); } else { currentPromise = resources.filePool.getFilePromise(region->sample); if (currentPromise == nullptr) { @@ -70,19 +72,6 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); } - if (region->oscillator || region->isGenerator()) { - float phase; - const float phaseParam = region->oscillatorPhase; - if (phaseParam >= 0) { - phase = phaseParam * (1.0f / 360.0f); - phase -= static_cast(phase); - } else { - std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; - phase = phaseDist(Random::randomGenerator); - } - waveOscillator.setPhase(phase); - } - pitchRatio = region->getBasePitchVariation(number, value); baseVolumedB = region->getBaseVolumedB(number); From 5e6de480fb68a4435ba4db28b59f842f89447621 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:22:36 +0100 Subject: [PATCH 10/16] Accidental fallthrough on rebase --- src/sfizz/Region.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index d21b3cc2..e22d6fb6 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -98,6 +98,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) // Wavetable oscillator case hash("oscillator_phase"): setValueFromOpcode(opcode, oscillatorPhase, Default::oscillatorPhaseRange); + break; case hash("oscillator"): if (auto value = readBooleanFromOpcode(opcode)) oscillator = *value; From b17f9eaf32ee682b70c35a0efd5a8adf6bcd50d0 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:23:14 +0100 Subject: [PATCH 11/16] Const all the region getters --- src/sfizz/Region.cpp | 24 ++++++++++++------------ src/sfizz/Region.h | 21 ++++++++------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index e22d6fb6..808f031e 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -52,14 +52,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("delay_random"): setValueFromOpcode(opcode, delayRandom, Default::delayRange); - delayDistribution.param(std::uniform_real_distribution::param_type(0, delayRandom)); break; case hash("offset"): setValueFromOpcode(opcode, offset, Default::offsetRange); break; case hash("offset_random"): setValueFromOpcode(opcode, offsetRandom, Default::offsetRange); - offsetDistribution.param(std::uniform_int_distribution::param_type(0, offsetRandom)); break; case hash("end"): setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange); @@ -301,7 +299,6 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("amp_random"): setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); - volumeDistribution.param(std::uniform_real_distribution::param_type(0, ampRandom)); break; case hash("amp_velcurve_&"): { @@ -643,7 +640,6 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("pitch_random"): setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange); - pitchDistribution.param(std::uniform_int_distribution::param_type(-pitchRandom, pitchRandom)); break; case hash("transpose"): setValueFromOpcode(opcode, transpose, Default::transposeRange); @@ -895,8 +891,9 @@ void sfz::Region::registerTempo(float secondsPerQuarter) noexcept bpmSwitched = false; } -float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept +float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept { + std::uniform_int_distribution pitchDistribution { -pitchRandom, pitchRandom }; auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center pitchVariationInCents += tune; // sample tuning pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose @@ -905,20 +902,21 @@ float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexc return centsFactor(pitchVariationInCents); } -float sfz::Region::getBaseVolumedB(int noteNumber) noexcept +float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept { + std::uniform_real_distribution volumeDistribution { -ampRandom, ampRandom }; auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key) baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber); return baseVolumedB; } -float sfz::Region::getBaseGain() noexcept +float sfz::Region::getBaseGain() const noexcept { return normalizePercents(amplitude); } -float sfz::Region::getPhase() noexcept +float sfz::Region::getPhase() const noexcept { float phase; if (oscillatorPhase >= 0) { @@ -931,13 +929,15 @@ float sfz::Region::getPhase() noexcept return phase; } -uint32_t sfz::Region::getOffset(Oversampling factor) noexcept +uint32_t sfz::Region::getOffset(Oversampling factor) const noexcept { + std::uniform_int_distribution offsetDistribution { 0, offsetRandom }; return (offset + offsetDistribution(Random::randomGenerator)) * static_cast(factor); } -float sfz::Region::getDelay() noexcept +float sfz::Region::getDelay() const noexcept { + std::uniform_real_distribution delayDistribution { 0, delayRandom }; return delay + delayDistribution(Random::randomGenerator); } @@ -988,7 +988,7 @@ float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCur return 1.0f; } -float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) noexcept +float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept { float baseGain { 1.0f }; @@ -1009,7 +1009,7 @@ float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) noexcept return baseGain; } -float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) noexcept +float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) const noexcept { float gain { 1.0f }; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index b3966a47..1fd0fa67 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -135,7 +135,7 @@ struct Region { * @param velocity * @return float */ - float getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept; + float getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept; /** * @brief Get the note-related gain of the region depending on which note has been * pressed and at which velocity. @@ -144,7 +144,7 @@ struct Region { * @param velocity * @return float */ - float getNoteGain(int noteNumber, uint8_t velocity) noexcept; + float getNoteGain(int noteNumber, uint8_t velocity) const noexcept; /** * @brief Get the additional crossfade gain of the region depending on the * CC values @@ -152,7 +152,7 @@ struct Region { * @param ccState * @return float */ - float getCrossfadeGain(const SfzCCArray& ccState) noexcept; + float getCrossfadeGain(const SfzCCArray& ccState) const noexcept; /** * @brief Get the base volume of the region depending on which note has been * pressed to trigger the region. @@ -160,19 +160,19 @@ struct Region { * @param noteNumber * @return float */ - float getBaseVolumedB(int noteNumber) noexcept; + float getBaseVolumedB(int noteNumber) const noexcept; /** * @brief Get the base gain of the region. * * @return float */ - float getBaseGain() noexcept; + float getBaseGain() const noexcept; /** * @brief Get the base gain of the region. * * @return float */ - float getPhase() noexcept; + float getPhase() const noexcept; /** * @brief Computes the gain value related to the velocity of the note * @@ -184,13 +184,13 @@ struct Region { * * @return uint32_t */ - uint32_t getOffset(Oversampling factor = Oversampling::x1) noexcept; + uint32_t getOffset(Oversampling factor = Oversampling::x1) const noexcept; /** * @brief Get the region delay in seconds * * @return float */ - float getDelay() noexcept; + float getDelay() const noexcept; /** * @brief Get the index of the sample end, either natural end or forced * loop. @@ -333,11 +333,6 @@ private: absl::string_view defaultPath { "" }; int sequenceCounter { 0 }; - - std::uniform_real_distribution volumeDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom }; - std::uniform_real_distribution delayDistribution { 0, sfz::Default::delayRandom }; - std::uniform_int_distribution offsetDistribution { 0, sfz::Default::offsetRandom }; - std::uniform_int_distribution pitchDistribution { -sfz::Default::pitchRandom, sfz::Default::pitchRandom }; LEAK_DETECTOR(Region); }; From 82999166cba4f842691bb21c1363deca9ab483f4 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:33:20 +0100 Subject: [PATCH 12/16] Add some checks when building the oscillator regions from files --- src/sfizz/Synth.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 347e7699..93b1475d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -362,7 +362,16 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) removeCurrentRegion(); } else if (region->oscillator && !region->isGenerator()) { - resources.wavePool.createFileWave(resources.filePool, region->sample); + if (!resources.filePool.checkSample(region->sample)) { + removeCurrentRegion(); + continue; + } + + const auto fileWave = resources.wavePool.createFileWave(resources.filePool, region->sample); + if (!fileWave) { + removeCurrentRegion(); + continue; + } } for (auto note = 0; note < 128; note++) { From f71e0b4f21d9f6e70636237fe47336184a9af7be Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 20:47:00 +0100 Subject: [PATCH 13/16] Revert passing the sample rate to createHarmonicProfile --- src/sfizz/Wavetables.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index df8a6b17..24a35bd7 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -361,7 +361,6 @@ const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const st if (!fileHandle) return nullptr; - if (fileHandle->information.numChannels > 1) DBG("[sfizz] Only the first channel of " << filename << " will be used to create the wavetable"); @@ -389,7 +388,7 @@ const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const st }; auto wave = std::make_shared( - WavetableMulti::createForHarmonicProfile(hp, 1.0, config::tableSize, fileHandle->information.sampleRate)); + WavetableMulti::createForHarmonicProfile(hp, 1.0)); _fileWaves[filename] = wave; return wave.get(); From fbf2785211250816e59d8061465e401795320ce6 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 21:03:03 +0100 Subject: [PATCH 14/16] createFileWave returns a boolean --- src/sfizz/Synth.cpp | 3 +-- src/sfizz/Wavetables.cpp | 10 +++++----- src/sfizz/Wavetables.h | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 93b1475d..9ef64406 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -367,8 +367,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) continue; } - const auto fileWave = resources.wavePool.createFileWave(resources.filePool, region->sample); - if (!fileWave) { + if (!resources.wavePool.createFileWave(resources.filePool, region->sample)) { removeCurrentRegion(); continue; } diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 24a35bd7..34b1e46a 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -352,14 +352,14 @@ void WavetablePool::clearFileWaves() _fileWaves.clear(); } -const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const std::string& filename) +bool WavetablePool::createFileWave(FilePool& filePool, const std::string& filename) { - if (const WavetableMulti* wave = getFileWave(filename)) - return wave; + if (_fileWaves.contains(filename)) + return true; auto fileHandle = filePool.loadFile(filename); if (!fileHandle) - return nullptr; + return false; if (fileHandle->information.numChannels > 1) DBG("[sfizz] Only the first channel of " << filename << " will be used to create the wavetable"); @@ -391,7 +391,7 @@ const WavetableMulti* WavetablePool::createFileWave(FilePool& filePool, const st WavetableMulti::createForHarmonicProfile(hp, 1.0)); _fileWaves[filename] = wave; - return wave.get(); + return true; } } // namespace sfz diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index f3ebd692..5bed0896 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -208,9 +208,9 @@ struct WavetablePool { * * @param filePool the file pool to use to load the file * @param filename the file name to load - * @return the wavetable + * @return true if the wavetable was correctly created (or existed already) */ - const WavetableMulti* createFileWave(FilePool& filePool, const std::string& filename); + bool createFileWave(FilePool& filePool, const std::string& filename); /** * @brief Removes all the stored file waves from the wavetable pool. */ From 9d8bfa5b82757a674023737058b906f52ec6f675 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 21:11:30 +0100 Subject: [PATCH 15/16] Change the static silent wavetable into a functionThe previous form tripped the leak detector --- src/sfizz/Wavetables.cpp | 12 +++++------- src/sfizz/Wavetables.h | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 34b1e46a..c9d72362 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -11,12 +11,10 @@ namespace sfz { -static WavetableMulti silenceMulti = WavetableMulti::createSilence(); - void WavetableOscillator::init(double sampleRate) { _sampleInterval = 1.0 / sampleRate; - _multi = &silenceMulti; + _multi = WavetableMulti::getSilenceWavetable(); clear(); } @@ -27,7 +25,7 @@ void WavetableOscillator::clear() void WavetableOscillator::setWavetable(const WavetableMulti* wave) { - _multi = wave ? wave : &silenceMulti; + _multi = wave ? wave : WavetableMulti::getSilenceWavetable(); } void WavetableOscillator::setPhase(float phase) @@ -252,12 +250,12 @@ WavetableMulti WavetableMulti::createForHarmonicProfile( return wm; } -WavetableMulti WavetableMulti::createSilence() +WavetableMulti* WavetableMulti::getSilenceWavetable() { - WavetableMulti wm; + static WavetableMulti wm; wm.allocateStorage(1); wm.fillExtra(); - return wm; + return &wm; } void WavetableMulti::allocateStorage(unsigned tableSize) diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index 5bed0896..c8aca0f6 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -159,8 +159,8 @@ public: static WavetableMulti createForHarmonicProfile( const HarmonicProfile& hp, double amplitude, unsigned tableSize = config::tableSize, double refSampleRate = 44100.0); - // create the tiniest wavetable with null content for use with oscillators - static WavetableMulti createSilence(); + // get a tiny silent wavetable with null content for use with oscillators + static WavetableMulti* getSilenceWavetable(); private: // get a pointer to the beginning of the N-th table From 29fc84fb095dcc7b172fc2e1f73efbc48072f2f1 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 21:29:38 +0100 Subject: [PATCH 16/16] const getSilenceWavetable --- src/sfizz/Wavetables.cpp | 2 +- src/sfizz/Wavetables.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index c9d72362..c65b244a 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -250,7 +250,7 @@ WavetableMulti WavetableMulti::createForHarmonicProfile( return wm; } -WavetableMulti* WavetableMulti::getSilenceWavetable() +const WavetableMulti* WavetableMulti::getSilenceWavetable() { static WavetableMulti wm; wm.allocateStorage(1); diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index c8aca0f6..6290f01f 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -160,7 +160,7 @@ public: const HarmonicProfile& hp, double amplitude, unsigned tableSize = config::tableSize, double refSampleRate = 44100.0); // get a tiny silent wavetable with null content for use with oscillators - static WavetableMulti* getSilenceWavetable(); + static const WavetableMulti* getSilenceWavetable(); private: // get a pointer to the beginning of the N-th table