Added sound file preloading

This commit is contained in:
paul 2019-08-04 01:38:31 +02:00
parent 834a907c2f
commit e2940e4794
11 changed files with 155 additions and 15 deletions

View file

@ -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

View file

@ -6,8 +6,11 @@ template<class ValueType>
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<int, ValueType> container;

View file

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

View file

@ -1,8 +1,12 @@
#pragma once
#include "StereoBuffer.h"
#include "Defaults.h"
#include <sndfile.hh>
#include <filesystem>
#include <map>
#include <optional>
#include <string_view>
#include <absl/container/flat_hash_map.h>
#include <map>
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<StereoBuffer<float>> preloadedData;
};
std::optional<FileInformation> 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<uint32_t>(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<uint32_t>(config::preloadSize));
returnedValue.preloadedData = std::make_shared<StereoBuffer<float>>(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<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
Buffer<float> tempReadBuffer { config::preloadSize * 2 };
// std::map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
absl::flat_hash_map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData;
};
static inline FilePool filePool;
}

View file

@ -41,7 +41,7 @@ inline std::optional<ValueType> readOpcode(std::string_view value, const Range<V
{
float returnedValue;
if (!absl::SimpleAtof(value, &returnedValue))
return {};
return std::nullopt;
return validRange.clamp(returnedValue);
}

View file

@ -27,9 +27,7 @@ public:
constexpr Range(Type start, Type end) noexcept
: _start(start), _end(std::max(start, end)) {}
~Range() = default;
Type start() const noexcept { return getStart(); }
Type getStart() const noexcept { return _start; }
Type end() const noexcept { return getEnd(); }
Type getEnd() const noexcept { return _end; }
std::pair<Type, Type> getPair() const noexcept { return std::make_pair<Type, Type>(_start, _end); }
Range(const Range<Type>& 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<Type>(0.0) };
Type _end { static_cast<Type>(0.0) };

View file

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

View file

@ -3,6 +3,7 @@
#include <vector>
#include <string>
#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<StereoBuffer<float>> preloadedData { nullptr };
private:
FilePool& filePool;
};
} // namespace sfz

View file

@ -1,6 +1,8 @@
#include "Synth.h"
#include "Helpers.h"
#include <iostream>
#include <utility>
#include <algorithm>
void sfz::Synth::callback(std::string_view header, std::vector<Opcode> members)
{
@ -42,11 +44,11 @@ void sfz::Synth::callback(std::string_view header, std::vector<Opcode> members)
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
{
auto& lastRegion = regions.emplace_back();
auto lastRegion = std::make_shared<Region>(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<Opcode>& 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<Opcode>& 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<Opcode>& 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;
}

View file

@ -2,6 +2,7 @@
#include "Parser.h"
#include "Region.h"
#include "SfzHelpers.h"
#include "FilePool.h"
#include <vector>
#include <set>
#include <optional>
@ -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() ? &regions[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<Opcode> members) final;
@ -35,11 +36,15 @@ private:
std::vector<Opcode> masterOpcodes;
std::vector<Opcode> groupOpcodes;
FilePool filePool;
CCValueArray ccState;
std::vector<CCNamePair> ccNames;
std::optional<uint8_t> defaultSwitch;
std::set<std::string_view> unknownOpcodes;
std::vector<Region> regions;
using RegionPtrVector = std::vector<std::shared_ptr<Region>>;
std::vector<std::shared_ptr<Region>> regions;
std::array<RegionPtrVector, 128> noteActivationLists;
std::array<RegionPtrVector, 128> ccActivationLists;
void buildRegion(const std::vector<Opcode>& regionOpcodes);
};

View file

@ -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<int> intRange {2, 10};
intRange.shrinkIfSmaller(0, 10);
REQUIRE( intRange == Range<int>(2, 10) );
intRange.shrinkIfSmaller(2, 11);
REQUIRE( intRange == Range<int>(2, 10) );
intRange.shrinkIfSmaller(2, 9);
REQUIRE( intRange == Range<int>(2, 9) );
intRange.shrinkIfSmaller(3, 9);
REQUIRE( intRange == Range<int>(3, 9) );
intRange.shrinkIfSmaller(4, 7);
REQUIRE( intRange == Range<int>(4, 7) );
intRange.shrinkIfSmaller(6, 5);
REQUIRE( intRange == Range<int>(5, 6) );
}