Support the direction opcode

This commit is contained in:
Jean Pierre Cimalando 2020-04-15 21:35:16 +02:00
parent e4103600ec
commit b6a437e4a4
9 changed files with 189 additions and 73 deletions

View file

@ -5,6 +5,7 @@ add_subdirectory(external/cpuid)
set (SFIZZ_SOURCES set (SFIZZ_SOURCES
sfizz/Synth.cpp sfizz/Synth.cpp
sfizz/FileId.cpp
sfizz/FilePool.cpp sfizz/FilePool.cpp
sfizz/FilterPool.cpp sfizz/FilterPool.cpp
sfizz/EQPool.cpp sfizz/EQPool.cpp

24
src/sfizz/FileId.cpp Normal file
View file

@ -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 <iostream>
size_t std::hash<sfz::FileId>::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;
}

67
src/sfizz/FileId.h Normal file
View file

@ -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 <string>
#include <iosfwd>
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<sfz::FileId> {
size_t operator()(const sfz::FileId &id) const;
};
}
std::ostream &operator<<(std::ostream &os, const sfz::FileId &fileId);

View file

@ -32,32 +32,47 @@
#include "absl/types/span.h" #include "absl/types/span.h"
#include "absl/strings/match.h" #include "absl/strings/match.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include <algorithm>
#include <memory> #include <memory>
#include <sndfile.hh> #include <sndfile.hh>
#include <thread> #include <thread>
template <class T> template <class T>
void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer<T>& output, uint32_t numFrames) void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer<T>& output, uint32_t numFrames, bool reverse)
{ {
output.reset(); output.reset();
output.resize(numFrames); output.resize(numFrames);
if (sndFile.channels() == 1) {
if (reverse)
sndFile.seek(-static_cast<sf_count_t>(numFrames), SEEK_END);
const unsigned channels = sndFile.channels();
if (channels == 1) {
output.addChannel(); output.addChannel();
sndFile.readf(output.channelWriter(0), numFrames); sndFile.readf(output.channelWriter(0), numFrames);
} else if (sndFile.channels() == 2) { } else if (channels == 2) {
output.addChannel(); output.addChannel();
output.addChannel(); output.addChannel();
sfz::Buffer<T> tempReadBuffer { 2 * numFrames }; sfz::Buffer<T> tempReadBuffer { 2 * numFrames };
sndFile.readf(tempReadBuffer.data(), numFrames); sndFile.readf(tempReadBuffer.data(), numFrames);
sfz::readInterleaved<T>(tempReadBuffer, output.getSpan(0), output.getSpan(1)); sfz::readInterleaved<T>(tempReadBuffer, output.getSpan(0), output.getSpan(1));
} }
if (reverse) {
for (unsigned c = 0; c < channels; ++c) {
// TODO: consider optimizing with SIMD
absl::Span<float> channel = output.getSpan(c);
std::reverse(channel.begin(), channel.end());
}
}
} }
template <class T> template <class T>
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor) std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse)
{ {
auto baseBuffer = absl::make_unique<sfz::AudioBuffer<T>>(); auto baseBuffer = absl::make_unique<sfz::AudioBuffer<T>>();
readBaseFile(sndFile, *baseBuffer, numFrames); readBaseFile(sndFile, *baseBuffer, numFrames, reverse);
if (factor == sfz::Oversampling::x1) if (factor == sfz::Oversampling::x1)
return baseBuffer; return baseBuffer;
@ -69,16 +84,16 @@ std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32
} }
template <class T> template <class T>
void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, sfz::AudioBuffer<float>& output, std::atomic<size_t>* filledFrames=nullptr) void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::AudioBuffer<float>& output, std::atomic<size_t>* filledFrames = nullptr)
{ {
if (factor == sfz::Oversampling::x1) { if (factor == sfz::Oversampling::x1) {
readBaseFile(sndFile, output, numFrames); readBaseFile(sndFile, output, numFrames, reverse);
if (filledFrames != nullptr) if (filledFrames != nullptr)
filledFrames->store(numFrames); filledFrames->store(numFrames);
return; return;
} }
auto baseBuffer = readFromFile<T>(sndFile, numFrames, sfz::Oversampling::x1); auto baseBuffer = readFromFile<T>(sndFile, numFrames, sfz::Oversampling::x1, reverse);
output.reset(); output.reset();
output.addChannels(baseBuffer->getNumChannels()); output.addChannels(baseBuffer->getNumChannels());
output.resize(numFrames * static_cast<int>(factor)); output.resize(numFrames * static_cast<int>(factor));
@ -175,16 +190,16 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept
#endif #endif
} }
absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const FileId& fileId) noexcept
{ {
fs::path file { rootDirectory / filename }; const fs::path file { rootDirectory / fileId.filename };
if (!fs::exists(file)) if (!fs::exists(file))
return {}; return {};
SndfileHandle sndFile(file.string().c_str()); SndfileHandle sndFile(file.string().c_str());
if (sndFile.channels() != 1 && sndFile.channels() != 2) { 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 {}; return {};
} }
@ -193,23 +208,28 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const std
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate()); returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
returnedValue.numChannels = sndFile.channels(); returnedValue.numChannels = sndFile.channels();
SF_INSTRUMENT instrumentInfo; if (!fileId.reverse) {
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); SF_INSTRUMENT instrumentInfo;
if (instrumentInfo.loop_count > 0) { sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));
returnedValue.loopBegin = instrumentInfo.loops[0].start; if (instrumentInfo.loop_count > 0) {
returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1); 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; 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(fileId);
auto fileInformation = getFileInformation(filename);
if (!fileInformation) if (!fileInformation)
return false; return false;
const fs::path file { rootDirectory / fileId.filename };
SndfileHandle sndFile(file.string().c_str()); SndfileHandle sndFile(file.string().c_str());
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same // 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); return min(frames, maxOffset + preloadSize);
}(); }();
const auto existingFile = preloadedFiles.find(filename); const auto existingFile = preloadedFiles.find(fileId);
if (existingFile != preloadedFiles.end()) { if (existingFile != preloadedFiles.end()) {
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
preloadedFiles[filename].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor); preloadedFiles[fileId].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor, fileId.reverse);
} }
} else { } else {
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate()); fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
FileDataHandle handle { FileDataHandle handle {
readFromFile<float>(sndFile, framesToLoad, oversamplingFactor), readFromFile<float>(sndFile, framesToLoad, oversamplingFactor, fileId.reverse),
*fileInformation *fileInformation
}; };
preloadedFiles.insert_or_assign(filename, handle); preloadedFiles.insert_or_assign(fileId, handle);
} }
return true; return true;
} }
absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const std::string& filename) noexcept absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const FileId& fileId) noexcept
{ {
fs::path file { rootDirectory / filename }; auto fileInformation = getFileInformation(fileId);
auto fileInformation = getFileInformation(filename);
if (!fileInformation) if (!fileInformation)
return {}; return {};
const fs::path file { rootDirectory / fileId.filename };
SndfileHandle sndFile(file.string().c_str()); SndfileHandle sndFile(file.string().c_str());
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
const auto frames = static_cast<uint32_t>(sndFile.frames()); const auto frames = static_cast<uint32_t>(sndFile.frames());
const auto existingFile = loadedFiles.find(filename); const auto existingFile = loadedFiles.find(fileId);
if (existingFile != loadedFiles.end()) { if (existingFile != loadedFiles.end()) {
return existingFile->second; return existingFile->second;
} else { } else {
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate()); fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
FileDataHandle handle { FileDataHandle handle {
readFromFile<float>(sndFile, frames, oversamplingFactor), readFromFile<float>(sndFile, frames, oversamplingFactor, fileId.reverse),
*fileInformation *fileInformation
}; };
loadedFiles.insert_or_assign(filename, handle); loadedFiles.insert_or_assign(fileId, handle);
return 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()) { 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 {}; return {};
} }
const auto preloaded = preloadedFiles.find(filename); const auto preloaded = preloadedFiles.find(fileId);
if (preloaded == preloadedFiles.end()) { 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 {}; return {};
} }
auto promise = emptyPromises.back(); auto promise = emptyPromises.back();
promise->filename = preloaded->first; promise->fileId = preloaded->first;
promise->preloadedData = preloaded->second.preloadedData; promise->preloadedData = preloaded->second.preloadedData;
promise->sampleRate = static_cast<float>(preloaded->second.information.sampleRate); promise->sampleRate = static_cast<float>(preloaded->second.information.sampleRate);
promise->oversamplingFactor = oversamplingFactor; promise->oversamplingFactor = oversamplingFactor;
promise->creationTime = std::chrono::high_resolution_clock::now(); promise->creationTime = std::chrono::high_resolution_clock::now();
if (!promiseQueue.try_push(promise)) { 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 {}; return {};
} }
@ -302,9 +322,9 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
for (auto& preloadedFile : preloadedFiles) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor); const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor);
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0; const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(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()); SndfileHandle sndFile(file.string().c_str());
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, oversamplingFactor); preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.reverse);
} }
this->preloadSize = preloadSize; this->preloadSize = preloadSize;
} }
@ -352,23 +372,23 @@ void sfz::FilePool::loadingThread() noexcept
const auto loadStartTime = std::chrono::high_resolution_clock::now(); const auto loadStartTime = std::chrono::high_resolution_clock::now();
const auto waitDuration = loadStartTime - promise->creationTime; 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()); SndfileHandle sndFile(file.string().c_str());
if (sndFile.error() != 0) { 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; promise->dataStatus = FilePromise::DataStatus::Error;
continue; continue;
} }
const auto frames = static_cast<uint32_t>(sndFile.frames()); const auto frames = static_cast<uint32_t>(sndFile.frames());
streamFromFile<float>(sndFile, frames, oversamplingFactor, promise->fileData, &promise->availableFrames); streamFromFile<float>(sndFile, frames, oversamplingFactor, promise->fileId.reverse, promise->fileData, &promise->availableFrames);
promise->dataStatus = FilePromise::DataStatus::Ready; promise->dataStatus = FilePromise::DataStatus::Ready;
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime; 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--; threadsLoading--;
while (!filledPromiseQueue.try_push(promise)) { 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)); 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) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor); const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor);
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0; const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(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()); SndfileHandle sndFile(file.string().c_str());
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, factor); preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.reverse);
preloadedFile.second.information.sampleRate *= samplerateChange; preloadedFile.second.information.sampleRate *= samplerateChange;
} }

View file

@ -30,6 +30,7 @@
#include "RTSemaphore.h" #include "RTSemaphore.h"
#include "AudioBuffer.h" #include "AudioBuffer.h"
#include "AudioSpan.h" #include "AudioSpan.h"
#include "FileId.h"
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include "ghc/fs_std.hpp" #include "ghc/fs_std.hpp"
#include <absl/container/flat_hash_map.h> #include <absl/container/flat_hash_map.h>
@ -44,7 +45,6 @@
namespace sfz { namespace sfz {
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>; using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
struct FileInformation { struct FileInformation {
uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t end { Default::sampleEndRange.getEnd() };
uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopBegin { Default::loopRange.getStart() };
@ -76,7 +76,7 @@ struct FilePromise
{ {
fileData.reset(); fileData.reset();
preloadedData.reset(); preloadedData.reset();
filename = ""; fileId = FileId {};
availableFrames = 0; availableFrames = 0;
dataStatus = DataStatus::Wait; dataStatus = DataStatus::Wait;
oversamplingFactor = config::defaultOversamplingFactor; oversamplingFactor = config::defaultOversamplingFactor;
@ -95,7 +95,7 @@ struct FilePromise
Error, Error,
}; };
absl::string_view filename {}; FileId fileId {};
AudioBufferPtr preloadedData {}; AudioBufferPtr preloadedData {};
AudioBuffer<float> fileData {}; AudioBuffer<float> fileData {};
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };
@ -155,30 +155,30 @@ public:
/** /**
* @brief Get metadata information about a file. * @brief Get metadata information about a file.
* *
* @param filename * @param fileId
* @return absl::optional<FileInformation> * @return absl::optional<FileInformation>
*/ */
absl::optional<FileInformation> getFileInformation(const std::string& filename) noexcept; absl::optional<FileInformation> getFileInformation(const FileId& fileId) noexcept;
/** /**
* @brief Preload a file with the proper offset bounds * @brief Preload a file with the proper offset bounds
* *
* @param filename * @param fileId
* @param offset the maximum offset to consider for preloading. The total preloaded * @param maxOffset the maximum offset to consider for preloading. The total preloaded
* size will be preloadSize + offset * size will be preloadSize + offset
* @return true if the preloading went fine * @return true if the preloading went fine
* @return false if something went wrong () * @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 * @brief Load a file and return its information. The file pool will store this
* data for future requests so use this function responsibly. * data for future requests so use this function responsibly.
* *
* @param filename * @param fileId
* @return A handle on the file data * @return A handle on the file data
*/ */
absl::optional<sfz::FileDataHandle> loadFile(const std::string& filename) noexcept; absl::optional<sfz::FileDataHandle> loadFile(const FileId& fileId) noexcept;
/** /**
* @brief Check that the sample exists. If not, try to find it in a case insensitive way. * @brief Check that the sample exists. If not, try to find it in a case insensitive way.
@ -204,10 +204,10 @@ public:
/** /**
* @brief Get a file promise * @brief Get a file promise
* *
* @param filename the file to preload * @param fileId the file to preload
* @return FilePromisePtr a file promise * @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 * @brief Change the preloading size. This will trigger a full
* reload of all samples, so don't call it on the audio thread. * reload of all samples, so don't call it on the audio thread.
@ -270,8 +270,8 @@ private:
std::mutex promiseGuard; std::mutex promiseGuard;
// Preloaded data // Preloaded data
absl::flat_hash_map<absl::string_view, FileDataHandle> preloadedFiles; absl::flat_hash_map<FileId, FileDataHandle> preloadedFiles;
absl::flat_hash_map<absl::string_view, FileDataHandle> loadedFiles; absl::flat_hash_map<FileId, FileDataHandle> loadedFiles;
std::vector<std::thread> threadPool { }; std::vector<std::thread> threadPool { };
LEAK_DETECTOR(FilePool); LEAK_DETECTOR(FilePool);
}; };

View file

@ -42,11 +42,14 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break; break;
if (trimmedSample[0] == '*') if (trimmedSample[0] == '*')
sample = std::string(trimmedSample); sampleId.filename = std::string(trimmedSample);
else else
sample = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } })); sampleId.filename = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } }));
} }
break; break;
case hash("direction"):
sampleId.reverse = opcode.value == "reverse";
break;
case hash("delay"): case hash("delay"):
setValueFromOpcode(opcode, delay, Default::delayRange); setValueFromOpcode(opcode, delay, Default::delayRange);
break; break;

View file

@ -14,6 +14,7 @@
#include "Opcode.h" #include "Opcode.h"
#include "AudioBuffer.h" #include "AudioBuffer.h"
#include "MidiState.h" #include "MidiState.h"
#include "FileId.h"
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include <bitset> #include <bitset>
#include <string> #include <string>
@ -57,7 +58,7 @@ struct Region {
* @return true * @return true
* @return false * @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)? * @brief Is stereo (has stereo sample or is unison oscillator)?
* *
@ -227,7 +228,7 @@ struct Region {
float getGainToEffectBus(unsigned number) const noexcept; float getGainToEffectBus(unsigned number) const noexcept;
// Sound source: sample playback // Sound source: sample playback
std::string sample {}; // Sample FileId sampleId {}; // Sample
float delay { Default::delay }; // delay float delay { Default::delay }; // delay
float delayRandom { Default::delayRandom }; // delay_random float delayRandom { Default::delayRandom }; // delay_random
int64_t offset { Default::offset }; // offset int64_t offset { Default::offset }; // offset

View file

@ -332,7 +332,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
if (currentRegion->get() == nullptr) if (currentRegion->get() == nullptr)
return; return;
DBG("Removing the region with sample " << currentRegion->get()->sample); DBG("Removing the region with sample " << currentRegion->get()->sampleId);
std::iter_swap(currentRegion, lastRegion); std::iter_swap(currentRegion, lastRegion);
++lastRegion; ++lastRegion;
}; };
@ -344,12 +344,12 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
auto region = currentRegion->get(); auto region = currentRegion->get();
if (!region->oscillator && !region->isGenerator()) { if (!region->oscillator && !region->isGenerator()) {
if (!resources.filePool.checkSample(region->sample)) { if (!resources.filePool.checkSample(region->sampleId.filename)) {
removeCurrentRegion(); removeCurrentRegion();
continue; continue;
} }
const auto fileInformation = resources.filePool.getFileInformation(region->sample); const auto fileInformation = resources.filePool.getFileInformation(region->sampleId);
if (!fileInformation) { if (!fileInformation) {
removeCurrentRegion(); removeCurrentRegion();
continue; continue;
@ -381,16 +381,16 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
return Default::offsetCCRange.clamp(sumOffsetCC); return Default::offsetCCRange.clamp(sumOffsetCC);
}(); }();
if (!resources.filePool.preloadFile(region->sample, maxOffset)) if (!resources.filePool.preloadFile(region->sampleId, maxOffset))
removeCurrentRegion(); removeCurrentRegion();
} }
else if (region->oscillator && !region->isGenerator()) { else if (region->oscillator && !region->isGenerator()) {
if (!resources.filePool.checkSample(region->sample)) { if (!resources.filePool.checkSample(region->sampleId.filename)) {
removeCurrentRegion(); removeCurrentRegion();
continue; continue;
} }
if (!resources.wavePool.createFileWave(resources.filePool, region->sample)) { if (!resources.wavePool.createFileWave(resources.filePool, region->sampleId.filename)) {
removeCurrentRegion(); removeCurrentRegion();
continue; continue;
} }

View file

@ -40,7 +40,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
if (region->isGenerator()) { if (region->isGenerator()) {
const WavetableMulti* wave = nullptr; const WavetableMulti* wave = nullptr;
switch (hash(region->sample)) { switch (hash(region->sampleId.filename)) {
default: default:
case hash("*silence"): case hash("*silence"):
break; break;
@ -64,14 +64,14 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
} }
setupOscillatorUnison(); setupOscillatorUnison();
} else if (region->oscillator) { } 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) { for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave); osc.setWavetable(wave);
osc.setPhase(region->getPhase()); osc.setPhase(region->getPhase());
} }
setupOscillatorUnison(); setupOscillatorUnison();
} else { } else {
currentPromise = resources.filePool.getFilePromise(region->sample); currentPromise = resources.filePool.getFilePromise(region->sampleId);
if (currentPromise == nullptr) { if (currentPromise == nullptr) {
reset(); reset();
return; return;
@ -483,7 +483,7 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
DBG("[sfizz] Underflow: source available samples " DBG("[sfizz] Underflow: source available samples "
<< source.getNumFrames() << "/" << source.getNumFrames() << "/"
<< region->trueSampleEnd(currentPromise->oversamplingFactor) << region->trueSampleEnd(currentPromise->oversamplingFactor)
<< " for sample " << region->sample); << " for sample " << region->sampleId);
} }
fill<int>(indices->last(remainingElements), sampleEnd); fill<int>(indices->last(remainingElements), sampleEnd);
fill<float>(leftCoeffs->last(remainingElements), 0.0f); fill<float>(leftCoeffs->last(remainingElements), 0.0f);
@ -529,7 +529,7 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
const auto leftSpan = buffer.getSpan(0); const auto leftSpan = buffer.getSpan(0);
const auto rightSpan = buffer.getSpan(1); 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(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); }); absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
} else { } else {