Added sound file preloading
This commit is contained in:
parent
834a907c2f
commit
e2940e4794
11 changed files with 155 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() ? ®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<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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) );
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue