Adapt the newer parser to Sfizz
This commit is contained in:
parent
9019ae3aef
commit
c03345e950
9 changed files with 138 additions and 42 deletions
|
|
@ -210,11 +210,11 @@ int main(int argc, char** argv)
|
|||
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
|
||||
std::cout << "==========" << '\n';
|
||||
std::cout << "Included files:" << '\n';
|
||||
for (auto& file : synth.getIncludedFiles())
|
||||
std::cout << '\t' << file.string() << '\n';
|
||||
for (auto& file : synth.getParser().getIncludedFiles())
|
||||
std::cout << '\t' << file << '\n';
|
||||
std::cout << "==========" << '\n';
|
||||
std::cout << "Defines:" << '\n';
|
||||
for (auto& define : synth.getDefines())
|
||||
for (auto& define : synth.getParser().getDefines())
|
||||
std::cout << '\t' << define.first << '=' << define.second << '\n';
|
||||
std::cout << "==========" << '\n';
|
||||
std::cout << "Unknown opcodes:";
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@
|
|||
#include <cctype>
|
||||
|
||||
sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue)
|
||||
: opcode(inputOpcode)
|
||||
, value(inputValue)
|
||||
: opcode(trim(inputOpcode))
|
||||
, value(trim(inputValue))
|
||||
{
|
||||
trimInPlace(value);
|
||||
trimInPlace(opcode);
|
||||
size_t nextCharIndex { 0 };
|
||||
int parameterPosition { 0 };
|
||||
auto nextNumIndex = opcode.find_first_of("1234567890");
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ namespace sfz {
|
|||
struct Opcode {
|
||||
Opcode() = delete;
|
||||
Opcode(absl::string_view inputOpcode, absl::string_view inputValue);
|
||||
absl::string_view opcode {};
|
||||
absl::string_view value {};
|
||||
std::string opcode {};
|
||||
std::string value {};
|
||||
uint64_t lettersOnlyHash { Fnv1aBasis };
|
||||
// This is to handle the integer parameters of some opcodes
|
||||
std::vector<uint16_t> parameters;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ sfz::Synth::Synth()
|
|||
|
||||
sfz::Synth::Synth(int numVoices)
|
||||
{
|
||||
parser.setListener(this);
|
||||
|
||||
effectFactory.registerStandardEffectTypes();
|
||||
|
||||
effectBuses.reserve(5); // sufficient room for main and fx1-4
|
||||
|
|
@ -48,7 +50,7 @@ sfz::Synth::~Synth()
|
|||
resources.filePool.emptyFileLoadingQueues();
|
||||
}
|
||||
|
||||
void sfz::Synth::callback(absl::string_view header, const std::vector<Opcode>& members)
|
||||
void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector<Opcode>& members)
|
||||
{
|
||||
switch (hash(header)) {
|
||||
case hash("global"):
|
||||
|
|
@ -81,6 +83,16 @@ void sfz::Synth::callback(absl::string_view header, const std::vector<Opcode>& m
|
|||
}
|
||||
}
|
||||
|
||||
void sfz::Synth::onParseError(const SourceRange& range, const std::string& message)
|
||||
{
|
||||
std::cerr << "Parse error L" << range.start.lineNumber << ": " << message << '\n';
|
||||
}
|
||||
|
||||
void sfz::Synth::onParseWarning(const SourceRange& range, const std::string& message)
|
||||
{
|
||||
std::cerr << "Parse warning L" << range.start.lineNumber << ": " << message << '\n';
|
||||
}
|
||||
|
||||
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||
{
|
||||
auto lastRegion = absl::make_unique<Region>(resources.midiState, defaultPath);
|
||||
|
|
@ -283,14 +295,15 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
|||
}
|
||||
|
||||
clear();
|
||||
auto parserReturned = sfz::OldParser::loadSfzFile(file);
|
||||
if (!parserReturned)
|
||||
|
||||
parser.parseFile(file);
|
||||
if (parser.getErrorCount() > 0)
|
||||
return false;
|
||||
|
||||
if (regions.empty())
|
||||
return false;
|
||||
|
||||
resources.filePool.setRootDirectory(this->originalDirectory);
|
||||
resources.filePool.setRootDirectory(parser.originalDirectory());
|
||||
|
||||
auto currentRegion = regions.begin();
|
||||
auto lastRegion = regions.rbegin();
|
||||
|
|
@ -403,7 +416,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
|||
voice->setMaxEQsPerVoice(maxEQs);
|
||||
}
|
||||
|
||||
return parserReturned;
|
||||
return true;
|
||||
}
|
||||
|
||||
sfz::Voice* sfz::Synth::findFreeVoice() noexcept
|
||||
|
|
@ -939,7 +952,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
|
|||
fs::file_time_type sfz::Synth::checkModificationTime()
|
||||
{
|
||||
auto returnedTime = modificationTime;
|
||||
for (const auto& file: getIncludedFiles()) {
|
||||
for (const auto& file: parser.getIncludedFiles()) {
|
||||
const auto fileTime = fs::last_write_time(file);
|
||||
if (returnedTime < fileTime)
|
||||
returnedTime = fileTime;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "LeakDetector.h"
|
||||
#include "MidiState.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "parser/Parser.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <absl/types/optional.h>
|
||||
#include <random>
|
||||
|
|
@ -59,7 +60,7 @@ namespace sfz {
|
|||
* The jack_client.cpp file contains examples of the most classical usage of the
|
||||
* synth and can be used as a reference.
|
||||
*/
|
||||
class Synth : public OldParser {
|
||||
class Synth final : public Parser::Listener {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Synth object with no voices. If you want sound
|
||||
|
|
@ -88,7 +89,7 @@ public:
|
|||
* @return true
|
||||
* @return false if the file was not found or no regions were loaded.
|
||||
*/
|
||||
bool loadSfzFile(const fs::path& file) final;
|
||||
bool loadSfzFile(const fs::path& file);
|
||||
/**
|
||||
* @brief Get the current number of regions loaded
|
||||
*
|
||||
|
|
@ -373,6 +374,20 @@ public:
|
|||
*
|
||||
*/
|
||||
void allSoundOff() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the parser.
|
||||
*
|
||||
* @return A reference to the parser.
|
||||
*/
|
||||
Parser& getParser() noexcept { return parser; }
|
||||
/**
|
||||
* @brief Get the parser.
|
||||
*
|
||||
* @return A reference to the parser.
|
||||
*/
|
||||
const Parser& getParser() const noexcept { return parser; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The parser callback; this is called by the parent object each time
|
||||
|
|
@ -382,7 +397,17 @@ protected:
|
|||
* @param header the header for the set of opcodes
|
||||
* @param members the opcode members
|
||||
*/
|
||||
void callback(absl::string_view header, const std::vector<Opcode>& members) final;
|
||||
void onParseFullBlock(const std::string& header, const std::vector<Opcode>& members) override;
|
||||
|
||||
/**
|
||||
* @brief The parser callback when an error occurs.
|
||||
*/
|
||||
void onParseError(const SourceRange& range, const std::string& message) override;
|
||||
|
||||
/**
|
||||
* @brief The parser callback when a warning occurs.
|
||||
*/
|
||||
void onParseWarning(const SourceRange& range, const std::string& message) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
|
@ -505,6 +530,7 @@ private:
|
|||
|
||||
Duration dispatchDuration { 0 };
|
||||
|
||||
Parser parser;
|
||||
fs::file_time_type modificationTime { };
|
||||
|
||||
LEAK_DETECTOR(Synth);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ void Parser::addDefinition(absl::string_view id, absl::string_view value)
|
|||
void Parser::parseFile(const fs::path& path)
|
||||
{
|
||||
_pathsIncluded.clear();
|
||||
_lastHeader.clear();
|
||||
_currentHeader.reset();
|
||||
_currentOpcodes.clear();
|
||||
_errorCount = 0;
|
||||
_warningCount = 0;
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ void Parser::parseFile(const fs::path& path)
|
|||
|
||||
includeNewFile(path);
|
||||
processTopLevel();
|
||||
flushCurrentHeader();
|
||||
|
||||
if (_listener)
|
||||
_listener->onParseEnd();
|
||||
|
|
@ -45,15 +47,28 @@ void Parser::includeNewFile(const fs::path& path)
|
|||
fs::path fullPath =
|
||||
(path.empty() || path.is_absolute()) ? path : _originalDirectory / path;
|
||||
|
||||
if (!_pathsIncluded.insert(fullPath.string()).second)
|
||||
if (_pathsIncluded.empty())
|
||||
_originalDirectory = fullPath.parent_path();
|
||||
else if (_pathsIncluded.find(fullPath.string()) != _pathsIncluded.end()) {
|
||||
if (_recursiveIncludeGuardEnabled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_included.size() == _maxIncludeDepth) {
|
||||
SourceLocation loc;
|
||||
loc.filePath = std::make_shared<fs::path>(fullPath);
|
||||
emitError({ loc, loc }, "Exceeded maximum include depth (" + std::to_string(_maxIncludeDepth) + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
auto fileReader = absl::make_unique<FileReader>(fullPath);
|
||||
if (fileReader->hasError()) {
|
||||
SourceLocation loc = fileReader->location();
|
||||
emitError({ loc, loc }, "Cannot open file for reading: " + fullPath.string());
|
||||
return;
|
||||
}
|
||||
|
||||
_pathsIncluded.insert(fullPath.string());
|
||||
_included.push_back(std::move(fileReader));
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +189,9 @@ void Parser::processHeader()
|
|||
return;
|
||||
}
|
||||
|
||||
_lastHeader = name;
|
||||
flushCurrentHeader();
|
||||
|
||||
_currentHeader = name;
|
||||
if (_listener)
|
||||
_listener->onParseHeader({ start, end }, name);
|
||||
}
|
||||
|
|
@ -245,10 +262,12 @@ void Parser::processOpcode()
|
|||
}
|
||||
SourceLocation valueEnd = reader.location();
|
||||
|
||||
if (_lastHeader.empty())
|
||||
if (!_currentHeader)
|
||||
emitWarning({ opcodeStart, valueEnd }, "The opcode is not under any header.");
|
||||
|
||||
std::string valueExpanded = expandDollarVars({ valueStart, valueEnd }, valueRaw);
|
||||
_currentOpcodes.emplace_back(nameExpanded, valueExpanded);
|
||||
|
||||
if (_listener)
|
||||
_listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueExpanded);
|
||||
}
|
||||
|
|
@ -275,6 +294,17 @@ void Parser::recover()
|
|||
reader.skipWhile([](char c) { return c != '\n'; });
|
||||
}
|
||||
|
||||
void Parser::flushCurrentHeader()
|
||||
{
|
||||
if (_currentHeader) {
|
||||
if (_listener)
|
||||
_listener->onParseFullBlock(*_currentHeader, _currentOpcodes);
|
||||
_currentHeader.reset();
|
||||
}
|
||||
|
||||
_currentOpcodes.clear();
|
||||
}
|
||||
|
||||
bool Parser::hasComment(Reader& reader)
|
||||
{
|
||||
if (reader.peekChar() != '/')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "../Opcode.h"
|
||||
#include "ghc/fs_std.hpp"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include <string>
|
||||
|
|
@ -28,17 +30,32 @@ public:
|
|||
void addDefinition(absl::string_view id, absl::string_view value);
|
||||
void parseFile(const fs::path& path);
|
||||
|
||||
void setRecursiveIncludeGuardEnabled(bool en) { _recursiveIncludeGuardEnabled = en; }
|
||||
void setMaximumIncludeDepth(size_t depth) { _maxIncludeDepth = depth; }
|
||||
|
||||
const fs::path& originalDirectory() const noexcept { return _originalDirectory; }
|
||||
|
||||
typedef absl::flat_hash_set<std::string> IncludeFileSet;
|
||||
typedef absl::flat_hash_map<std::string, std::string> DefinitionSet;
|
||||
|
||||
const IncludeFileSet& getIncludedFiles() const noexcept { return _pathsIncluded; }
|
||||
const DefinitionSet& getDefines() const noexcept { return _definitions; }
|
||||
|
||||
size_t getErrorCount() const noexcept { return _errorCount; }
|
||||
size_t getWarningCount() const noexcept { return _warningCount; }
|
||||
|
||||
class Listener {
|
||||
public:
|
||||
virtual void onParseBegin() = 0;
|
||||
virtual void onParseEnd() = 0;
|
||||
virtual void onParseHeader(const SourceRange& range, const std::string& header) = 0;
|
||||
virtual void onParseOpcode(const SourceRange& rangeOpcode, const SourceRange& rangeValue, const std::string& name, const std::string& value) = 0;
|
||||
virtual void onParseError(const SourceRange& range, const std::string& message) = 0;
|
||||
virtual void onParseWarning(const SourceRange& range, const std::string& message) = 0;
|
||||
// low-level parsing
|
||||
virtual void onParseBegin() {}
|
||||
virtual void onParseEnd() {}
|
||||
virtual void onParseHeader(const SourceRange& /*range*/, const std::string& /*header*/) {}
|
||||
virtual void onParseOpcode(const SourceRange& /*rangeOpcode*/, const SourceRange& /*rangeValue*/, const std::string& /*name*/, const std::string& /*value*/) {}
|
||||
virtual void onParseError(const SourceRange& /*range*/, const std::string& /*message*/) {}
|
||||
virtual void onParseWarning(const SourceRange& /*range*/, const std::string& /*message*/) {}
|
||||
|
||||
// high-level parsing
|
||||
virtual void onParseFullBlock(const std::string& /*header*/, const std::vector<Opcode>& /*opcodes*/) {}
|
||||
};
|
||||
|
||||
void setListener(Listener* listener) noexcept { _listener = listener; }
|
||||
|
|
@ -57,6 +74,9 @@ private:
|
|||
// recover after error
|
||||
void recover();
|
||||
|
||||
// state handling
|
||||
void flushCurrentHeader();
|
||||
|
||||
// helpers
|
||||
static bool hasComment(Reader& reader);
|
||||
static size_t skipComment(Reader& reader);
|
||||
|
|
@ -73,16 +93,19 @@ private:
|
|||
Listener* _listener = nullptr;
|
||||
|
||||
fs::path _originalDirectory { fs::current_path() };
|
||||
absl::flat_hash_map<std::string, std::string> _definitions;
|
||||
DefinitionSet _definitions;
|
||||
|
||||
// a current list of files included, last one at the back
|
||||
std::vector<std::unique_ptr<Reader>> _included;
|
||||
|
||||
// recursive include guard
|
||||
absl::flat_hash_set<std::string> _pathsIncluded;
|
||||
size_t _maxIncludeDepth = 32;
|
||||
bool _recursiveIncludeGuardEnabled = false;
|
||||
IncludeFileSet _pathsIncluded;
|
||||
|
||||
// parsing state
|
||||
std::string _lastHeader;
|
||||
absl::optional<std::string> _currentHeader;
|
||||
std::vector<Opcode> _currentOpcodes;
|
||||
|
||||
// errors and warnings
|
||||
size_t _errorCount = 0;
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ TEST_CASE("[Files] Subdir include Win")
|
|||
TEST_CASE("[Files] Recursive include (with include guard)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.enableRecursiveIncludeGuard();
|
||||
sfz::Parser& parser = synth.getParser();
|
||||
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");
|
||||
|
|
@ -108,7 +109,8 @@ TEST_CASE("[Files] Recursive include (with include guard)")
|
|||
TEST_CASE("[Files] Include loops (with include guard)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.enableRecursiveIncludeGuard();
|
||||
sfz::Parser& parser = synth.getParser();
|
||||
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");
|
||||
|
|
@ -502,8 +504,9 @@ TEST_CASE("[Files] Case sentitiveness")
|
|||
TEST_CASE("[Files] Empty file")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::Parser& parser = synth.getParser();
|
||||
REQUIRE(!synth.loadSfzFile(""));
|
||||
REQUIRE(synth.getIncludedFiles().empty());
|
||||
REQUIRE(parser.getIncludedFiles().empty());
|
||||
REQUIRE(!synth.loadSfzFile({}));
|
||||
REQUIRE(synth.getIncludedFiles().empty());
|
||||
REQUIRE(parser.getIncludedFiles().empty());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
#include "sfizz/Curve.h"
|
||||
#include "sfizz/Parser.h"
|
||||
#include "sfizz/parser/Parser.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <iostream>
|
||||
|
|
@ -34,16 +34,16 @@ static void usage()
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Parser which extracts the configuration of curves
|
||||
* @brief Parser listener which extracts the configuration of curves
|
||||
*/
|
||||
class CurveParser : public sfz::OldParser {
|
||||
class CurveParserListener : public sfz::Parser::Listener {
|
||||
public:
|
||||
explicit CurveParser(sfz::CurveSet& curveSet)
|
||||
explicit CurveParserListener(sfz::CurveSet& curveSet)
|
||||
: curveSet(curveSet)
|
||||
{
|
||||
}
|
||||
|
||||
void callback(absl::string_view header, const std::vector<sfz::Opcode>& members) override
|
||||
void onParseFullBlock(const std::string& header, const std::vector<sfz::Opcode>& members) override
|
||||
{
|
||||
if (header == "curve")
|
||||
curveSet.addCurveFromHeader(members);
|
||||
|
|
@ -73,8 +73,11 @@ int main(int argc, char* argv[])
|
|||
sfz::CurveSet curveSet = sfz::CurveSet::createPredefined();
|
||||
|
||||
if (!filePath.empty()) {
|
||||
CurveParser parser(curveSet);
|
||||
if (!parser.loadSfzFile(filePath)) {
|
||||
sfz::Parser parser;
|
||||
CurveParserListener listener(curveSet);
|
||||
parser.setListener(&listener);
|
||||
parser.parseFile(filePath);
|
||||
if (parser.getErrorCount() > 0) {
|
||||
std::cerr << "Cannot load SFZ: " << filePath << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue