From b6a437e4a4206fc2ca86a58909fd310e64cf7a01 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 15 Apr 2020 21:35:16 +0200 Subject: [PATCH 1/3] Support the direction opcode --- src/CMakeLists.txt | 1 + src/sfizz/FileId.cpp | 24 +++++++++ src/sfizz/FileId.h | 67 +++++++++++++++++++++++++ src/sfizz/FilePool.cpp | 108 ++++++++++++++++++++++++----------------- src/sfizz/FilePool.h | 28 +++++------ src/sfizz/Region.cpp | 7 ++- src/sfizz/Region.h | 5 +- src/sfizz/Synth.cpp | 12 ++--- src/sfizz/Voice.cpp | 10 ++-- 9 files changed, 189 insertions(+), 73 deletions(-) create mode 100644 src/sfizz/FileId.cpp create mode 100644 src/sfizz/FileId.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b096fa3d..1a02ee538 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(external/cpuid) set (SFIZZ_SOURCES sfizz/Synth.cpp + sfizz/FileId.cpp sfizz/FilePool.cpp sfizz/FilterPool.cpp sfizz/EQPool.cpp diff --git a/src/sfizz/FileId.cpp b/src/sfizz/FileId.cpp new file mode 100644 index 000000000..8355ab708 --- /dev/null +++ b/src/sfizz/FileId.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "FileId.h" +#include "StringViewHelpers.h" +#include + +size_t std::hash::operator()(const sfz::FileId &id) const +{ + uint64_t h = ::hash(id.filename); + h = ::hash(id.reverse ? "!" : "", h); + return h; +} + +std::ostream &operator<<(std::ostream &os, const sfz::FileId &fileId) +{ + os << fileId.filename; + if (fileId.reverse) + os << " (reverse)"; + return os; +} diff --git a/src/sfizz/FileId.h b/src/sfizz/FileId.h new file mode 100644 index 000000000..7193bdd06 --- /dev/null +++ b/src/sfizz/FileId.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include +#include + +namespace sfz { + +/** + * @brief Sample file identifier within a file pool. + */ +struct FileId { + std::string filename; + bool reverse = false; + + /** + * @brief Construct a null identifier. + */ + FileId() + { + } + + /** + * @brief Construct a file identifier, optionally reversed. + * + * @param filename + * @param reverse + */ + FileId(std::string filename, bool reverse = false) + : filename(std::move(filename)), reverse(reverse) + { + } + + /** + * @brief Check equality with another identifier. + * + * @param other + */ + bool operator==(const FileId &other) const + { + return reverse == other.reverse && filename == other.filename; + } + + /** + * @brief Check inequality with another identifier. + * + * @param other + */ + bool operator!=(const FileId &other) const + { + return !operator==(other); + } +}; + +} + +namespace std { + template <> struct hash { + size_t operator()(const sfz::FileId &id) const; + }; +} + +std::ostream &operator<<(std::ostream &os, const sfz::FileId &fileId); diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index c474b6348..f9f38ae7d 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -32,32 +32,47 @@ #include "absl/types/span.h" #include "absl/strings/match.h" #include "absl/memory/memory.h" +#include #include #include #include template -void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t numFrames) +void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer& output, uint32_t numFrames, bool reverse) { output.reset(); output.resize(numFrames); - if (sndFile.channels() == 1) { + + if (reverse) + sndFile.seek(-static_cast(numFrames), SEEK_END); + + const unsigned channels = sndFile.channels(); + + if (channels == 1) { output.addChannel(); sndFile.readf(output.channelWriter(0), numFrames); - } else if (sndFile.channels() == 2) { + } else if (channels == 2) { output.addChannel(); output.addChannel(); sfz::Buffer tempReadBuffer { 2 * numFrames }; sndFile.readf(tempReadBuffer.data(), numFrames); sfz::readInterleaved(tempReadBuffer, output.getSpan(0), output.getSpan(1)); } + + if (reverse) { + for (unsigned c = 0; c < channels; ++c) { + // TODO: consider optimizing with SIMD + absl::Span channel = output.getSpan(c); + std::reverse(channel.begin(), channel.end()); + } + } } template -std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor) +std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse) { auto baseBuffer = absl::make_unique>(); - readBaseFile(sndFile, *baseBuffer, numFrames); + readBaseFile(sndFile, *baseBuffer, numFrames, reverse); if (factor == sfz::Oversampling::x1) return baseBuffer; @@ -69,16 +84,16 @@ std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32 } template -void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, sfz::AudioBuffer& output, std::atomic* filledFrames=nullptr) +void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::AudioBuffer& output, std::atomic* filledFrames = nullptr) { if (factor == sfz::Oversampling::x1) { - readBaseFile(sndFile, output, numFrames); + readBaseFile(sndFile, output, numFrames, reverse); if (filledFrames != nullptr) filledFrames->store(numFrames); return; } - auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1); + auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse); output.reset(); output.addChannels(baseBuffer->getNumChannels()); output.resize(numFrames * static_cast(factor)); @@ -175,16 +190,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 FileId& fileId) noexcept { - fs::path file { rootDirectory / filename }; + const fs::path file { rootDirectory / fileId.filename }; if (!fs::exists(file)) return {}; SndfileHandle sndFile(file.string().c_str()); if (sndFile.channels() != 1 && sndFile.channels() != 2) { - DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); + DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << fileId); return {}; } @@ -193,23 +208,28 @@ absl::optional sfz::FilePool::getFileInformation(const std returnedValue.sampleRate = static_cast(sndFile.samplerate()); returnedValue.numChannels = sndFile.channels(); - SF_INSTRUMENT instrumentInfo; - sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); - if (instrumentInfo.loop_count > 0) { - returnedValue.loopBegin = instrumentInfo.loops[0].start; - returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1); + if (!fileId.reverse) { + SF_INSTRUMENT instrumentInfo; + sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); + if (instrumentInfo.loop_count > 0) { + returnedValue.loopBegin = instrumentInfo.loops[0].start; + returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1); + } + } else { + // TODO loops ignored when reversed + // prehaps it can make use of SF_LOOP_BACKWARD? } return returnedValue; } -bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept +bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexcept { - fs::path file { rootDirectory / filename }; - auto fileInformation = getFileInformation(filename); + auto fileInformation = getFileInformation(fileId); if (!fileInformation) return false; + const fs::path file { rootDirectory / fileId.filename }; SndfileHandle sndFile(file.string().c_str()); // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same @@ -221,69 +241,69 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) return min(frames, maxOffset + preloadSize); }(); - const auto existingFile = preloadedFiles.find(filename); + const auto existingFile = preloadedFiles.find(fileId); if (existingFile != preloadedFiles.end()) { if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { - preloadedFiles[filename].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor); + preloadedFiles[fileId].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.reverse); } } else { fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); FileDataHandle handle { - readFromFile(sndFile, framesToLoad, oversamplingFactor), + readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.reverse), *fileInformation }; - preloadedFiles.insert_or_assign(filename, handle); + preloadedFiles.insert_or_assign(fileId, handle); } return true; } -absl::optional sfz::FilePool::loadFile(const std::string& filename) noexcept +absl::optional sfz::FilePool::loadFile(const FileId& fileId) noexcept { - fs::path file { rootDirectory / filename }; - auto fileInformation = getFileInformation(filename); + auto fileInformation = getFileInformation(fileId); if (!fileInformation) return {}; + const fs::path file { rootDirectory / fileId.filename }; 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); + const auto existingFile = loadedFiles.find(fileId); if (existingFile != loadedFiles.end()) { return existingFile->second; } else { fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); FileDataHandle handle { - readFromFile(sndFile, frames, oversamplingFactor), + readFromFile(sndFile, frames, oversamplingFactor, fileId.reverse), *fileInformation }; - loadedFiles.insert_or_assign(filename, handle); + loadedFiles.insert_or_assign(fileId, handle); return handle; } } -sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept +sfz::FilePromisePtr sfz::FilePool::getFilePromise(const FileId& fileId) noexcept { if (emptyPromises.empty()) { - DBG("[sfizz] No empty promises left to honor the one for " << filename); + DBG("[sfizz] No empty promises left to honor the one for " << fileId); return {}; } - const auto preloaded = preloadedFiles.find(filename); + const auto preloaded = preloadedFiles.find(fileId); if (preloaded == preloadedFiles.end()) { - DBG("[sfizz] File not found in the preloaded files: " << filename); + DBG("[sfizz] File not found in the preloaded files: " << fileId); return {}; } auto promise = emptyPromises.back(); - promise->filename = preloaded->first; + promise->fileId = preloaded->first; promise->preloadedData = preloaded->second.preloadedData; promise->sampleRate = static_cast(preloaded->second.information.sampleRate); promise->oversamplingFactor = oversamplingFactor; promise->creationTime = std::chrono::high_resolution_clock::now(); if (!promiseQueue.try_push(promise)) { - DBG("[sfizz] Could not enqueue the promise for " << filename << " (queue capacity " << promiseQueue.capacity() << ")"); + DBG("[sfizz] Could not enqueue the promise for " << fileId << " (queue capacity " << promiseQueue.capacity() << ")"); return {}; } @@ -302,9 +322,9 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept for (auto& preloadedFile : preloadedFiles) { const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast(oversamplingFactor); const auto maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; - fs::path file { rootDirectory / std::string(preloadedFile.first) }; + fs::path file { rootDirectory / preloadedFile.first.filename }; SndfileHandle sndFile(file.string().c_str()); - preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.reverse); } this->preloadSize = preloadSize; } @@ -352,23 +372,23 @@ void sfz::FilePool::loadingThread() noexcept const auto loadStartTime = std::chrono::high_resolution_clock::now(); const auto waitDuration = loadStartTime - promise->creationTime; - fs::path file { rootDirectory / std::string(promise->filename) }; + const fs::path file { rootDirectory / promise->fileId.filename }; SndfileHandle sndFile(file.string().c_str()); if (sndFile.error() != 0) { - DBG("[sfizz] libsndfile errored for " << promise->filename << " with message " << sndFile.strError()); + DBG("[sfizz] libsndfile errored for " << promise->fileId << " 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); + streamFromFile(sndFile, frames, oversamplingFactor, promise->fileId.reverse, promise->fileData, &promise->availableFrames); promise->dataStatus = FilePromise::DataStatus::Ready; const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime; - logger.logFileTime(waitDuration, loadDuration, frames, promise->filename); + logger.logFileTime(waitDuration, loadDuration, frames, promise->fileId.filename); threadsLoading--; while (!filledPromiseQueue.try_push(promise)) { - DBG("[sfizz] Error enqueuing the promise for " << promise->filename << " in the filledPromiseQueue"); + DBG("[sfizz] Error enqueuing the promise for " << promise->fileId << " in the filledPromiseQueue"); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -431,9 +451,9 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept for (auto& preloadedFile : preloadedFiles) { const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast(this->oversamplingFactor); const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; - fs::path file { rootDirectory / std::string(preloadedFile.first) }; + fs::path file { rootDirectory / preloadedFile.first.filename }; SndfileHandle sndFile(file.string().c_str()); - preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.reverse); preloadedFile.second.information.sampleRate *= samplerateChange; } diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index c2600ecee..631dd6f6c 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -30,6 +30,7 @@ #include "RTSemaphore.h" #include "AudioBuffer.h" #include "AudioSpan.h" +#include "FileId.h" #include "SIMDHelpers.h" #include "ghc/fs_std.hpp" #include @@ -44,7 +45,6 @@ namespace sfz { using AudioBufferPtr = std::shared_ptr>; - struct FileInformation { uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t loopBegin { Default::loopRange.getStart() }; @@ -76,7 +76,7 @@ struct FilePromise { fileData.reset(); preloadedData.reset(); - filename = ""; + fileId = FileId {}; availableFrames = 0; dataStatus = DataStatus::Wait; oversamplingFactor = config::defaultOversamplingFactor; @@ -95,7 +95,7 @@ struct FilePromise Error, }; - absl::string_view filename {}; + FileId fileId {}; AudioBufferPtr preloadedData {}; AudioBuffer fileData {}; float sampleRate { config::defaultSampleRate }; @@ -155,30 +155,30 @@ class FilePool { /** * @brief Get metadata information about a file. * - * @param filename + * @param fileId * @return absl::optional */ - absl::optional getFileInformation(const std::string& filename) noexcept; + absl::optional getFileInformation(const FileId& fileId) noexcept; /** * @brief Preload a file with the proper offset bounds * - * @param filename - * @param offset the maximum offset to consider for preloading. The total preloaded + * @param fileId + * @param maxOffset the maximum offset to consider for preloading. The total preloaded * size will be preloadSize + offset * @return true if the preloading went fine * @return false if something went wrong () */ - bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; + bool preloadFile(const FileId& fileId, 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 + * @param fileId * @return A handle on the file data */ - absl::optional loadFile(const std::string& filename) noexcept; + absl::optional loadFile(const FileId& fileId) noexcept; /** * @brief Check that the sample exists. If not, try to find it in a case insensitive way. @@ -204,10 +204,10 @@ class FilePool { /** * @brief Get a file promise * - * @param filename the file to preload + * @param fileId the file to preload * @return FilePromisePtr a file promise */ - FilePromisePtr getFilePromise(const std::string& filename) noexcept; + FilePromisePtr getFilePromise(const FileId& fileId) noexcept; /** * @brief Change the preloading size. This will trigger a full * reload of all samples, so don't call it on the audio thread. @@ -270,8 +270,8 @@ class FilePool { std::mutex promiseGuard; // Preloaded data - absl::flat_hash_map preloadedFiles; - absl::flat_hash_map loadedFiles; + absl::flat_hash_map preloadedFiles; + absl::flat_hash_map loadedFiles; std::vector threadPool { }; LEAK_DETECTOR(FilePool); }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 992618394..2d95854aa 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -42,11 +42,14 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; if (trimmedSample[0] == '*') - sample = std::string(trimmedSample); + sampleId.filename = std::string(trimmedSample); else - sample = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } })); + sampleId.filename = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } })); } break; + case hash("direction"): + sampleId.reverse = opcode.value == "reverse"; + break; case hash("delay"): setValueFromOpcode(opcode, delay, Default::delayRange); break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 9540b1cce..cd954671d 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -14,6 +14,7 @@ #include "Opcode.h" #include "AudioBuffer.h" #include "MidiState.h" +#include "FileId.h" #include "absl/types/optional.h" #include #include @@ -57,7 +58,7 @@ struct Region { * @return true * @return false */ - bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : false; } + bool isGenerator() const noexcept { return sampleId.filename.size() > 0 ? sampleId.filename[0] == '*' : false; } /** * @brief Is stereo (has stereo sample or is unison oscillator)? * @@ -227,7 +228,7 @@ struct Region { float getGainToEffectBus(unsigned number) const noexcept; // Sound source: sample playback - std::string sample {}; // Sample + FileId sampleId {}; // Sample float delay { Default::delay }; // delay float delayRandom { Default::delayRandom }; // delay_random int64_t offset { Default::offset }; // offset diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d52aa86c2..8f9f1b4f3 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -332,7 +332,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) if (currentRegion->get() == nullptr) return; - DBG("Removing the region with sample " << currentRegion->get()->sample); + DBG("Removing the region with sample " << currentRegion->get()->sampleId); std::iter_swap(currentRegion, lastRegion); ++lastRegion; }; @@ -344,12 +344,12 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) auto region = currentRegion->get(); if (!region->oscillator && !region->isGenerator()) { - if (!resources.filePool.checkSample(region->sample)) { + if (!resources.filePool.checkSample(region->sampleId.filename)) { removeCurrentRegion(); continue; } - const auto fileInformation = resources.filePool.getFileInformation(region->sample); + const auto fileInformation = resources.filePool.getFileInformation(region->sampleId); if (!fileInformation) { removeCurrentRegion(); continue; @@ -381,16 +381,16 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) return Default::offsetCCRange.clamp(sumOffsetCC); }(); - if (!resources.filePool.preloadFile(region->sample, maxOffset)) + if (!resources.filePool.preloadFile(region->sampleId, maxOffset)) removeCurrentRegion(); } else if (region->oscillator && !region->isGenerator()) { - if (!resources.filePool.checkSample(region->sample)) { + if (!resources.filePool.checkSample(region->sampleId.filename)) { removeCurrentRegion(); continue; } - if (!resources.wavePool.createFileWave(resources.filePool, region->sample)) { + if (!resources.wavePool.createFileWave(resources.filePool, region->sampleId.filename)) { removeCurrentRegion(); continue; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index cfa8bfbef..68650ef0c 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -40,7 +40,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (region->isGenerator()) { const WavetableMulti* wave = nullptr; - switch (hash(region->sample)) { + switch (hash(region->sampleId.filename)) { default: case hash("*silence"): break; @@ -64,14 +64,14 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, } setupOscillatorUnison(); } else if (region->oscillator) { - const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample); + const WavetableMulti* wave = resources.wavePool.getFileWave(region->sampleId.filename); for (WavetableOscillator& osc : waveOscillators) { osc.setWavetable(wave); osc.setPhase(region->getPhase()); } setupOscillatorUnison(); } else { - currentPromise = resources.filePool.getFilePromise(region->sample); + currentPromise = resources.filePool.getFilePromise(region->sampleId); if (currentPromise == nullptr) { reset(); return; @@ -483,7 +483,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept DBG("[sfizz] Underflow: source available samples " << source.getNumFrames() << "/" << region->trueSampleEnd(currentPromise->oversamplingFactor) - << " for sample " << region->sample); + << " for sample " << region->sampleId); } fill(indices->last(remainingElements), sampleEnd); fill(leftCoeffs->last(remainingElements), 0.0f); @@ -529,7 +529,7 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept const auto leftSpan = buffer.getSpan(0); const auto rightSpan = buffer.getSpan(1); - if (region->sample == "*noise") { + if (region->sampleId.filename == "*noise") { absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); }); absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); }); } else { From 32d7a0aa48eac6b780e7f9eb3a2d8d32dc840825 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 16 Apr 2020 05:48:08 +0200 Subject: [PATCH 2/3] Add a test for opcode `direction` --- tests/RegionT.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index c90ebc784..12c764bae 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -23,6 +23,15 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.sample == "dummy.wav"); } + SECTION("direction") + { + REQUIRE(!region.sampleId.reverse); + region.parseOpcode({ "direction", "reverse" }); + REQUIRE(region.sampleId.reverse); + region.parseOpcode({ "direction", "forward" }); + REQUIRE(!region.sampleId.reverse); + } + SECTION("delay") { REQUIRE(region.delay == 0.0); From ff2b710937e862bbcce7a6d882dc561e95c2ef6b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 16 Apr 2020 05:54:10 +0200 Subject: [PATCH 3/3] Update tests --- tests/FilesT.cpp | 112 +++++++++++++++++++++++----------------------- tests/RegionT.cpp | 4 +- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 4dd43c8fc..f332186c5 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -19,7 +19,7 @@ TEST_CASE("[Files] Single region (regions_one.sfz)") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_one.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); } @@ -28,9 +28,9 @@ TEST_CASE("[Files] Multiple regions (regions_many.sfz)") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_many.sfz"); REQUIRE(synth.getNumRegions() == 3); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy.1.wav"); - REQUIRE(synth.getRegionView(2)->sample == "dummy.2.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy.1.wav"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == "dummy.2.wav"); } TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)") @@ -54,8 +54,8 @@ TEST_CASE("[Files] (regions_bad.sfz)") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_bad.sfz"); REQUIRE(synth.getNumRegions() == 2); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy.wav"); } TEST_CASE("[Files] Local include") @@ -63,7 +63,7 @@ TEST_CASE("[Files] Local include") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_local.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); } TEST_CASE("[Files] Multiple includes") @@ -71,8 +71,8 @@ TEST_CASE("[Files] Multiple includes") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes.sfz"); REQUIRE(synth.getNumRegions() == 2); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy2.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy2.wav"); } TEST_CASE("[Files] Multiple includes with comments") @@ -80,8 +80,8 @@ TEST_CASE("[Files] Multiple includes with comments") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes_with_comments.sfz"); REQUIRE(synth.getNumRegions() == 2); - REQUIRE(synth.getRegionView(0)->sample == "dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy2.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy2.wav"); } TEST_CASE("[Files] Subdir include") @@ -89,7 +89,7 @@ TEST_CASE("[Files] Subdir include") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == "dummy_subdir.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_subdir.wav"); } TEST_CASE("[Files] Subdir include Win") @@ -97,7 +97,7 @@ TEST_CASE("[Files] Subdir include Win") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir_win.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == "dummy_subdir.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_subdir.wav"); } TEST_CASE("[Files] Recursive include (with include guard)") @@ -107,8 +107,8 @@ TEST_CASE("[Files] Recursive include (with include guard)") parser.setRecursiveIncludeGuardEnabled(true); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz"); REQUIRE(synth.getNumRegions() == 2); - REQUIRE(synth.getRegionView(0)->sample == "dummy_recursive2.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy_recursive1.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_recursive2.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy_recursive1.wav"); } TEST_CASE("[Files] Include loops (with include guard)") @@ -118,8 +118,8 @@ TEST_CASE("[Files] Include loops (with include guard)") parser.setRecursiveIncludeGuardEnabled(true); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz"); REQUIRE(synth.getNumRegions() == 2); - REQUIRE(synth.getRegionView(0)->sample == "dummy_loop2.wav"); - REQUIRE(synth.getRegionView(1)->sample == "dummy_loop1.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_loop2.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy_loop1.wav"); } TEST_CASE("[Files] Define test") @@ -205,28 +205,28 @@ TEST_CASE("[Files] Full hierarchy with antislashes") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz"); REQUIRE(synth.getNumRegions() == 8); - REQUIRE(synth.getRegionView(0)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(4)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(5)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(6)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(7)->sample == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(4)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(5)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(6)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(7)->sampleId.filename == "Regions/dummy.1.wav"); } { sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy_antislash.sfz"); REQUIRE(synth.getNumRegions() == 8); - REQUIRE(synth.getRegionView(0)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(4)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(5)->sample == "Regions/dummy.1.wav"); - REQUIRE(synth.getRegionView(6)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(7)->sample == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(4)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(5)->sampleId.filename == "Regions/dummy.1.wav"); + REQUIRE(synth.getRegionView(6)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(7)->sampleId.filename == "Regions/dummy.1.wav"); } } @@ -245,10 +245,10 @@ TEST_CASE("[Files] Pizz basic") REQUIRE(synth.getRegionView(1)->randRange == sfz::Range(0.25, 0.5)); REQUIRE(synth.getRegionView(2)->randRange == sfz::Range(0.5, 0.75)); REQUIRE(synth.getRegionView(3)->randRange == sfz::Range(0.75, 1.0)); - REQUIRE(synth.getRegionView(0)->sample == R"(../Samples/pizz/a0_vl4_rr1.wav)"); - REQUIRE(synth.getRegionView(1)->sample == R"(../Samples/pizz/a0_vl4_rr2.wav)"); - REQUIRE(synth.getRegionView(2)->sample == R"(../Samples/pizz/a0_vl4_rr3.wav)"); - REQUIRE(synth.getRegionView(3)->sample == R"(../Samples/pizz/a0_vl4_rr4.wav)"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr1.wav)"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr2.wav)"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr3.wav)"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr4.wav)"); } TEST_CASE("[Files] Channels (channels.sfz)") @@ -256,9 +256,9 @@ TEST_CASE("[Files] Channels (channels.sfz)") sfz::Synth synth; 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)->sampleId.filename == "mono_sample.wav"); REQUIRE(!synth.getRegionView(0)->isStereo()); - REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "stereo_sample.wav"); REQUIRE(synth.getRegionView(1)->isStereo()); } @@ -268,32 +268,32 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)") synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz"); REQUIRE(synth.getNumRegions() == 6); - REQUIRE(synth.getRegionView(0)->sample == "*sine"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "*sine"); REQUIRE(!synth.getRegionView(0)->isStereo()); REQUIRE(synth.getRegionView(0)->isGenerator()); REQUIRE(!synth.getRegionView(0)->oscillator); - REQUIRE(synth.getRegionView(1)->sample == "*sine"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "*sine"); REQUIRE(synth.getRegionView(1)->isStereo()); REQUIRE(synth.getRegionView(1)->isGenerator()); REQUIRE(!synth.getRegionView(1)->oscillator); - REQUIRE(synth.getRegionView(2)->sample == "ramp_wave.wav"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == "ramp_wave.wav"); REQUIRE(!synth.getRegionView(2)->isStereo()); REQUIRE(!synth.getRegionView(2)->isGenerator()); REQUIRE(synth.getRegionView(2)->oscillator); - REQUIRE(synth.getRegionView(3)->sample == "ramp_wave.wav"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == "ramp_wave.wav"); REQUIRE(synth.getRegionView(3)->isStereo()); REQUIRE(!synth.getRegionView(3)->isGenerator()); REQUIRE(synth.getRegionView(3)->oscillator); - REQUIRE(synth.getRegionView(4)->sample == "*sine"); + REQUIRE(synth.getRegionView(4)->sampleId.filename == "*sine"); REQUIRE(!synth.getRegionView(4)->isStereo()); REQUIRE(synth.getRegionView(4)->isGenerator()); REQUIRE(!synth.getRegionView(4)->oscillator); - REQUIRE(synth.getRegionView(5)->sample == "*sine"); + REQUIRE(synth.getRegionView(5)->sampleId.filename == "*sine"); REQUIRE(!synth.getRegionView(5)->isStereo()); REQUIRE(synth.getRegionView(5)->isGenerator()); REQUIRE(!synth.getRegionView(5)->oscillator); @@ -359,7 +359,7 @@ TEST_CASE("[Files] Specific bug: relative path with backslashes") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/win_backslashes.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == R"(Xylo/Subfolder/closedhat.wav)"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(Xylo/Subfolder/closedhat.wav)"); } TEST_CASE("[Files] Default path") @@ -367,10 +367,10 @@ TEST_CASE("[Files] Default path") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path.sfz"); REQUIRE(synth.getNumRegions() == 4); - REQUIRE(synth.getRegionView(0)->sample == R"(DefaultPath/SubPath1/sample1.wav)"); - REQUIRE(synth.getRegionView(1)->sample == R"(DefaultPath/SubPath2/sample2.wav)"); - REQUIRE(synth.getRegionView(2)->sample == R"(DefaultPath/SubPath1/sample1.wav)"); - REQUIRE(synth.getRegionView(3)->sample == R"(DefaultPath/SubPath2/sample2.wav)"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(DefaultPath/SubPath1/sample1.wav)"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == R"(DefaultPath/SubPath1/sample1.wav)"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)"); } TEST_CASE("[Files] Default path reset when calling loadSfzFile again") @@ -380,7 +380,7 @@ TEST_CASE("[Files] Default path reset when calling loadSfzFile again") REQUIRE(synth.getNumRegions() == 4); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_reset.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == R"(DefaultPath/SubPath2/sample2.wav)"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)"); } TEST_CASE("[Files] Default path is ignored for generators") @@ -388,7 +388,7 @@ TEST_CASE("[Files] Default path is ignored for generators") sfz::Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_generator.sfz"); REQUIRE(synth.getNumRegions() == 1); - REQUIRE(synth.getRegionView(0)->sample == R"(*sine)"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(*sine)"); } TEST_CASE("[Files] Set CC applies properly") @@ -547,10 +547,10 @@ TEST_CASE("[Files] Case sentitiveness") sfz::Synth synth; synth.loadSfzFile(sfzFilePath); REQUIRE(synth.getNumRegions() == 4); - REQUIRE(synth.getRegionView(0)->sample == "dummy1.wav"); - REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy1.wav"); + REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.wav"); } } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 12c764bae..7cc2864d6 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -18,9 +18,9 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("sample") { - REQUIRE(region.sample == ""); + REQUIRE(region.sampleId.filename == ""); region.parseOpcode({ "sample", "dummy.wav" }); - REQUIRE(region.sample == "dummy.wav"); + REQUIRE(region.sampleId.filename == "dummy.wav"); } SECTION("direction")