From e2940e4794d75ddc2a25192162c06a3ee71189b5 Mon Sep 17 00:00:00 2001 From: paul Date: Sun, 4 Aug 2019 01:38:31 +0200 Subject: [PATCH] Added sound file preloading --- CMakeLists.txt | 2 +- sources/CCMap.h | 8 +++++-- sources/EGDescription.h | 5 ++++ sources/FilePool.h | 51 +++++++++++++++++++++++++++++++++++++++-- sources/Opcode.h | 2 +- sources/Range.h | 14 ++++++++--- sources/Region.cpp | 13 ++++++++++- sources/Region.h | 9 ++++++++ sources/Synth.cpp | 40 +++++++++++++++++++++++++++++--- sources/Synth.h | 9 ++++++-- tests/RangeT.cpp | 17 ++++++++++++++ 11 files changed, 155 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e9d667f..18049146 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ add_executable(sfizz sources/Main.cpp ${COMMON_SOURCES}) if(UNIX) target_link_libraries(sfizz stdc++fs) endif(UNIX) -target_link_libraries(sfizz absl::strings cxxopts) +target_link_libraries(sfizz absl::strings cxxopts sndfile absl::flat_hash_map) ############################### # Test application diff --git a/sources/CCMap.h b/sources/CCMap.h index 913b36d8..8ab613aa 100644 --- a/sources/CCMap.h +++ b/sources/CCMap.h @@ -6,8 +6,11 @@ template class CCMap { public: - CCMap(const ValueType& defaultValue) - : defaultValue(defaultValue) { } + CCMap() = delete; + CCMap(const ValueType& defaultValue) : defaultValue(defaultValue) { } + CCMap(CCMap&&) = default; + CCMap(const CCMap&) = default; + ~CCMap() = default; const ValueType &getWithDefault(int index) const noexcept { @@ -32,6 +35,7 @@ public: inline bool empty() const { return container.empty(); } const ValueType &at(int index) const { return container.at(index); } bool contains(int index) const noexcept { return container.find(index) != end(container); } + private: const ValueType defaultValue; std::map container; diff --git a/sources/EGDescription.h b/sources/EGDescription.h index 4f7f3885..7fde245a 100644 --- a/sources/EGDescription.h +++ b/sources/EGDescription.h @@ -10,6 +10,11 @@ namespace sfz struct EGDescription { + EGDescription() = default; + EGDescription(const EGDescription&) = default; + EGDescription(EGDescription&&) = default; + ~EGDescription() = default; + float attack { Default::attack }; float decay { Default::decay }; float delay { Default::delayEG }; diff --git a/sources/FilePool.h b/sources/FilePool.h index 643ffe50..12ad76b4 100644 --- a/sources/FilePool.h +++ b/sources/FilePool.h @@ -1,8 +1,12 @@ #pragma once #include "StereoBuffer.h" +#include "Defaults.h" +#include #include -#include +#include #include +#include +#include namespace sfz { @@ -10,8 +14,51 @@ class FilePool { public: FilePool() = default; + void setRootDirectory(const std::filesystem::path& directory) + { + rootDirectory = directory; + } + + struct FileInformation + { + uint32_t end { Default::sampleEndRange.getEnd() }; + uint32_t loopBegin { Default::loopRange.getStart() }; + uint32_t loopEnd { Default::loopRange.getEnd() }; + std::shared_ptr> preloadedData; + }; + + std::optional getFileInformation(std::string_view filename) + { + std::filesystem::path file { rootDirectory / filename }; + if (!std::filesystem::exists(file)) + return {}; + + SndfileHandle sndFile { file.c_str() }; + FileInformation returnedValue; + returnedValue.end = static_cast(sndFile.frames()); + SF_INSTRUMENT instrumentInfo; + sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); + if (instrumentInfo.loop_count == 1) + { + returnedValue.loopBegin = instrumentInfo.loops[0].start; + returnedValue.loopEnd = instrumentInfo.loops[0].end; + } + auto preloadedSize = std::min(returnedValue.end, static_cast(config::preloadSize)); + returnedValue.preloadedData = std::make_shared>(preloadedSize); + sndFile.readf(tempReadBuffer.data(), preloadedSize); + returnedValue.preloadedData->readInterleaved(tempReadBuffer.data(), preloadedSize); + preloadedData[filename] = returnedValue.preloadedData; + // char buffer [2048] ; + // sndFile.command(SFC_GET_LOG_INFO, buffer, sizeof(buffer)) ; + // DBG(buffer); + return returnedValue; + } private: std::filesystem::path rootDirectory; - std::map>> preloadedData; + Buffer tempReadBuffer { config::preloadSize * 2 }; + // std::map>> preloadedData; + absl::flat_hash_map>> preloadedData; }; + +static inline FilePool filePool; } \ No newline at end of file diff --git a/sources/Opcode.h b/sources/Opcode.h index 6285bb05..492b3f8f 100644 --- a/sources/Opcode.h +++ b/sources/Opcode.h @@ -41,7 +41,7 @@ inline std::optional readOpcode(std::string_view value, const Range getPair() const noexcept { return std::make_pair(_start, _end); } Range(const Range& range) = default; @@ -47,10 +45,20 @@ public: if (end < _start) _start = end; } - Type clamp(Type value) const noexcept { return std::clamp(value, _start, _end); } bool containsWithEnd(Type value) const noexcept { return (value >= _start && value <= _end); } bool contains(Type value) const noexcept { return (value >= _start && value < _end); } + void shrinkIfSmaller( Type start, Type end) + { + if (start > end) + std::swap(start, end); + + if (start > _start) + _start = start; + + if (end < _end) + _end = end; + } private: Type _start { static_cast(0.0) }; Type _end { static_cast(0.0) }; diff --git a/sources/Region.cpp b/sources/Region.cpp index 7ff88840..1eaf8482 100644 --- a/sources/Region.cpp +++ b/sources/Region.cpp @@ -245,16 +245,27 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) bool sfz::Region::prepare() { - return false; + auto fileInformation = filePool.getFileInformation(sample); + if (!fileInformation) + return false; + + DBG("Sample " << sample << " information: " << fileInformation->end << "(" << fileInformation->loopBegin << "->" << fileInformation->loopEnd << ")"); + sampleEnd = std::min(sampleEnd, fileInformation->end); + loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd); + preloadedData = fileInformation->preloadedData; + return true; } + bool sfz::Region::isSwitchedOn() const noexcept { return false; } + bool sfz::Region::registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue) { return false; } + bool sfz::Region::registerNoteOff(int channel, int noteNumber, uint8_t velocity, float randValue) { return false; diff --git a/sources/Region.h b/sources/Region.h index 7daa9b81..43281d83 100644 --- a/sources/Region.h +++ b/sources/Region.h @@ -3,6 +3,7 @@ #include #include #include "Opcode.h" +#include "FilePool.h" #include "EGDescription.h" #include "Defaults.h" #include "CCMap.h" @@ -10,6 +11,11 @@ namespace sfz { struct Region { + Region() = delete; + Region(FilePool& pool): filePool(pool) {} + Region(const Region&) = default; + ~Region() = default; + bool isRelease() const noexcept { return trigger == SfzTrigger::release || trigger == SfzTrigger::release_key; } bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : false; } bool shouldLoop() const noexcept { return (loopMode == SfzLoopMode::loop_continuous || loopMode == SfzLoopMode::loop_sustain); } @@ -105,5 +111,8 @@ struct Region double sampleRate { config::defaultSampleRate }; int numChannels { 1 }; + std::shared_ptr> preloadedData { nullptr }; +private: + FilePool& filePool; }; } // namespace sfz \ No newline at end of file diff --git a/sources/Synth.cpp b/sources/Synth.cpp index 1691981c..0ffb60fd 100644 --- a/sources/Synth.cpp +++ b/sources/Synth.cpp @@ -1,6 +1,8 @@ #include "Synth.h" #include "Helpers.h" #include +#include +#include void sfz::Synth::callback(std::string_view header, std::vector members) { @@ -42,11 +44,11 @@ void sfz::Synth::callback(std::string_view header, std::vector members) void sfz::Synth::buildRegion(const std::vector& regionOpcodes) { - auto& lastRegion = regions.emplace_back(); + auto lastRegion = std::make_shared(filePool); auto parseOpcodes = [&](const auto& opcodes) { for (auto& opcode: opcodes) - if (!lastRegion.parseOpcode(opcode)) + if (!lastRegion->parseOpcode(opcode)) unknownOpcodes.insert(opcode.opcode); }; @@ -54,6 +56,8 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) parseOpcodes(masterOpcodes); parseOpcodes(groupOpcodes); parseOpcodes(regionOpcodes); + + regions.push_back(lastRegion); } void sfz::Synth::clear() @@ -99,6 +103,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) case hash("default_path"): if (auto newPath = std::filesystem::path(member.value); std::filesystem::exists(newPath)) rootDirectory = newPath; + break; default: // Unsupported control opcode ASSERTFALSE; @@ -109,5 +114,34 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) { clear(); - return sfz::Parser::loadSfzFile(filename); + auto parserReturned = sfz::Parser::loadSfzFile(filename); + if (!parserReturned) + return false; + + if (regions.empty()) + return false; + + filePool.setRootDirectory(this->rootDirectory); + auto lastRegion = regions.end() - 1; + auto currentRegion = regions.begin(); + while (currentRegion <= lastRegion) + { + if (! (*currentRegion)->prepare() ) + { + DBG("Removing the region with sample " << (*currentRegion)->sample); + std::iter_swap(currentRegion, lastRegion); + lastRegion--; + } + else + { + for (auto note = (*currentRegion)->keyRange.getStart(); note <= (*currentRegion)->keyRange.getEnd(); note++) + noteActivationLists[note].push_back(*currentRegion); + for (auto cc = (*currentRegion)->keyRange.getStart(); cc <= (*currentRegion)->keyRange.getEnd(); cc++) + ccActivationLists[cc].push_back(*currentRegion); + currentRegion++; + } + } + DBG("Removed " << regions.size() - std::distance(regions.begin(), lastRegion) - 1 << " out of " << regions.size() << " regions."); + regions.resize(std::distance(regions.begin(), lastRegion) + 1); + return parserReturned; } diff --git a/sources/Synth.h b/sources/Synth.h index 35759467..9605bf2d 100644 --- a/sources/Synth.h +++ b/sources/Synth.h @@ -2,6 +2,7 @@ #include "Parser.h" #include "Region.h" #include "SfzHelpers.h" +#include "FilePool.h" #include #include #include @@ -18,7 +19,7 @@ public: int getNumGroups() const noexcept { return numGroups; } int getNumMasters() const noexcept { return numMasters; } int getNumCurves() const noexcept { return numCurves; } - const Region* getRegionView(int idx) const noexcept { return idx < regions.size() ? ®ions[idx] : nullptr; } + const Region* getRegionView(int idx) const noexcept { return (size_t)idx < regions.size() ? regions[idx].get() : nullptr; } auto getUnknownOpcodes() { return unknownOpcodes; } protected: void callback(std::string_view header, std::vector members) final; @@ -35,11 +36,15 @@ private: std::vector masterOpcodes; std::vector groupOpcodes; + FilePool filePool; CCValueArray ccState; std::vector ccNames; std::optional defaultSwitch; std::set unknownOpcodes; - std::vector regions; + using RegionPtrVector = std::vector>; + std::vector> regions; + std::array noteActivationLists; + std::array ccActivationLists; void buildRegion(const std::vector& regionOpcodes); }; diff --git a/tests/RangeT.cpp b/tests/RangeT.cpp index fc39c7de..72fe8391 100644 --- a/tests/RangeT.cpp +++ b/tests/RangeT.cpp @@ -71,4 +71,21 @@ TEST_CASE("[Range] Clamp") REQUIRE( floatRange.clamp(5.0) == 5.0_a ); REQUIRE( floatRange.clamp(10.0) == 10.0_a ); REQUIRE( floatRange.clamp(11.0) == 10.0_a ); +} + +TEST_CASE("[Range] shrinkIfSmaller") +{ + Range intRange {2, 10}; + intRange.shrinkIfSmaller(0, 10); + REQUIRE( intRange == Range(2, 10) ); + intRange.shrinkIfSmaller(2, 11); + REQUIRE( intRange == Range(2, 10) ); + intRange.shrinkIfSmaller(2, 9); + REQUIRE( intRange == Range(2, 9) ); + intRange.shrinkIfSmaller(3, 9); + REQUIRE( intRange == Range(3, 9) ); + intRange.shrinkIfSmaller(4, 7); + REQUIRE( intRange == Range(4, 7) ); + intRange.shrinkIfSmaller(6, 5); + REQUIRE( intRange == Range(5, 6) ); } \ No newline at end of file