Adapt the newer parser to Sfizz

This commit is contained in:
Jean Pierre Cimalando 2020-03-17 22:14:35 +01:00 committed by Paul Ferrand
parent 9019ae3aef
commit c03345e950
9 changed files with 138 additions and 42 deletions

View file

@ -210,11 +210,11 @@ int main(int argc, char** argv)
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n'; std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
std::cout << "==========" << '\n'; std::cout << "==========" << '\n';
std::cout << "Included files:" << '\n'; std::cout << "Included files:" << '\n';
for (auto& file : synth.getIncludedFiles()) for (auto& file : synth.getParser().getIncludedFiles())
std::cout << '\t' << file.string() << '\n'; std::cout << '\t' << file << '\n';
std::cout << "==========" << '\n'; std::cout << "==========" << '\n';
std::cout << "Defines:" << '\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 << '\t' << define.first << '=' << define.second << '\n';
std::cout << "==========" << '\n'; std::cout << "==========" << '\n';
std::cout << "Unknown opcodes:"; std::cout << "Unknown opcodes:";

View file

@ -9,11 +9,9 @@
#include <cctype> #include <cctype>
sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue)
: opcode(inputOpcode) : opcode(trim(inputOpcode))
, value(inputValue) , value(trim(inputValue))
{ {
trimInPlace(value);
trimInPlace(opcode);
size_t nextCharIndex { 0 }; size_t nextCharIndex { 0 };
int parameterPosition { 0 }; int parameterPosition { 0 };
auto nextNumIndex = opcode.find_first_of("1234567890"); auto nextNumIndex = opcode.find_first_of("1234567890");

View file

@ -28,8 +28,8 @@ namespace sfz {
struct Opcode { struct Opcode {
Opcode() = delete; Opcode() = delete;
Opcode(absl::string_view inputOpcode, absl::string_view inputValue); Opcode(absl::string_view inputOpcode, absl::string_view inputValue);
absl::string_view opcode {}; std::string opcode {};
absl::string_view value {}; std::string value {};
uint64_t lettersOnlyHash { Fnv1aBasis }; uint64_t lettersOnlyHash { Fnv1aBasis };
// This is to handle the integer parameters of some opcodes // This is to handle the integer parameters of some opcodes
std::vector<uint16_t> parameters; std::vector<uint16_t> parameters;

View file

@ -28,6 +28,8 @@ sfz::Synth::Synth()
sfz::Synth::Synth(int numVoices) sfz::Synth::Synth(int numVoices)
{ {
parser.setListener(this);
effectFactory.registerStandardEffectTypes(); effectFactory.registerStandardEffectTypes();
effectBuses.reserve(5); // sufficient room for main and fx1-4 effectBuses.reserve(5); // sufficient room for main and fx1-4
@ -48,7 +50,7 @@ sfz::Synth::~Synth()
resources.filePool.emptyFileLoadingQueues(); 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)) { switch (hash(header)) {
case hash("global"): 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) void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
{ {
auto lastRegion = absl::make_unique<Region>(resources.midiState, defaultPath); auto lastRegion = absl::make_unique<Region>(resources.midiState, defaultPath);
@ -283,14 +295,15 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
} }
clear(); clear();
auto parserReturned = sfz::OldParser::loadSfzFile(file);
if (!parserReturned) parser.parseFile(file);
if (parser.getErrorCount() > 0)
return false; return false;
if (regions.empty()) if (regions.empty())
return false; return false;
resources.filePool.setRootDirectory(this->originalDirectory); resources.filePool.setRootDirectory(parser.originalDirectory());
auto currentRegion = regions.begin(); auto currentRegion = regions.begin();
auto lastRegion = regions.rbegin(); auto lastRegion = regions.rbegin();
@ -403,7 +416,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
voice->setMaxEQsPerVoice(maxEQs); voice->setMaxEQsPerVoice(maxEQs);
} }
return parserReturned; return true;
} }
sfz::Voice* sfz::Synth::findFreeVoice() noexcept sfz::Voice* sfz::Synth::findFreeVoice() noexcept
@ -939,7 +952,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
fs::file_time_type sfz::Synth::checkModificationTime() fs::file_time_type sfz::Synth::checkModificationTime()
{ {
auto returnedTime = modificationTime; auto returnedTime = modificationTime;
for (const auto& file: getIncludedFiles()) { for (const auto& file: parser.getIncludedFiles()) {
const auto fileTime = fs::last_write_time(file); const auto fileTime = fs::last_write_time(file);
if (returnedTime < fileTime) if (returnedTime < fileTime)
returnedTime = fileTime; returnedTime = fileTime;

View file

@ -14,6 +14,7 @@
#include "LeakDetector.h" #include "LeakDetector.h"
#include "MidiState.h" #include "MidiState.h"
#include "AudioSpan.h" #include "AudioSpan.h"
#include "parser/Parser.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <absl/types/optional.h> #include <absl/types/optional.h>
#include <random> #include <random>
@ -59,7 +60,7 @@ namespace sfz {
* The jack_client.cpp file contains examples of the most classical usage of the * The jack_client.cpp file contains examples of the most classical usage of the
* synth and can be used as a reference. * synth and can be used as a reference.
*/ */
class Synth : public OldParser { class Synth final : public Parser::Listener {
public: public:
/** /**
* @brief Construct a new Synth object with no voices. If you want sound * @brief Construct a new Synth object with no voices. If you want sound
@ -88,7 +89,7 @@ public:
* @return true * @return true
* @return false if the file was not found or no regions were loaded. * @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 * @brief Get the current number of regions loaded
* *
@ -373,6 +374,20 @@ public:
* *
*/ */
void allSoundOff() noexcept; 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: protected:
/** /**
* @brief The parser callback; this is called by the parent object each time * @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 header the header for the set of opcodes
* @param members the opcode members * @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: private:
/** /**
@ -505,6 +530,7 @@ private:
Duration dispatchDuration { 0 }; Duration dispatchDuration { 0 };
Parser parser;
fs::file_time_type modificationTime { }; fs::file_time_type modificationTime { };
LEAK_DETECTOR(Synth); LEAK_DETECTOR(Synth);

View file

@ -26,7 +26,8 @@ void Parser::addDefinition(absl::string_view id, absl::string_view value)
void Parser::parseFile(const fs::path& path) void Parser::parseFile(const fs::path& path)
{ {
_pathsIncluded.clear(); _pathsIncluded.clear();
_lastHeader.clear(); _currentHeader.reset();
_currentOpcodes.clear();
_errorCount = 0; _errorCount = 0;
_warningCount = 0; _warningCount = 0;
@ -35,6 +36,7 @@ void Parser::parseFile(const fs::path& path)
includeNewFile(path); includeNewFile(path);
processTopLevel(); processTopLevel();
flushCurrentHeader();
if (_listener) if (_listener)
_listener->onParseEnd(); _listener->onParseEnd();
@ -45,15 +47,28 @@ void Parser::includeNewFile(const fs::path& path)
fs::path fullPath = fs::path fullPath =
(path.empty() || path.is_absolute()) ? path : _originalDirectory / path; (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; return;
}
auto fileReader = absl::make_unique<FileReader>(fullPath); auto fileReader = absl::make_unique<FileReader>(fullPath);
if (fileReader->hasError()) { if (fileReader->hasError()) {
SourceLocation loc = fileReader->location(); SourceLocation loc = fileReader->location();
emitError({ loc, loc }, "Cannot open file for reading: " + fullPath.string()); emitError({ loc, loc }, "Cannot open file for reading: " + fullPath.string());
return;
} }
_pathsIncluded.insert(fullPath.string());
_included.push_back(std::move(fileReader)); _included.push_back(std::move(fileReader));
} }
@ -174,7 +189,9 @@ void Parser::processHeader()
return; return;
} }
_lastHeader = name; flushCurrentHeader();
_currentHeader = name;
if (_listener) if (_listener)
_listener->onParseHeader({ start, end }, name); _listener->onParseHeader({ start, end }, name);
} }
@ -245,10 +262,12 @@ void Parser::processOpcode()
} }
SourceLocation valueEnd = reader.location(); SourceLocation valueEnd = reader.location();
if (_lastHeader.empty()) if (!_currentHeader)
emitWarning({ opcodeStart, valueEnd }, "The opcode is not under any header."); emitWarning({ opcodeStart, valueEnd }, "The opcode is not under any header.");
std::string valueExpanded = expandDollarVars({ valueStart, valueEnd }, valueRaw); std::string valueExpanded = expandDollarVars({ valueStart, valueEnd }, valueRaw);
_currentOpcodes.emplace_back(nameExpanded, valueExpanded);
if (_listener) if (_listener)
_listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueExpanded); _listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueExpanded);
} }
@ -275,6 +294,17 @@ void Parser::recover()
reader.skipWhile([](char c) { return c != '\n'; }); 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) bool Parser::hasComment(Reader& reader)
{ {
if (reader.peekChar() != '/') if (reader.peekChar() != '/')

View file

@ -5,7 +5,9 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once #pragma once
#include "../Opcode.h"
#include "ghc/fs_std.hpp" #include "ghc/fs_std.hpp"
#include "absl/types/optional.h"
#include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h" #include "absl/container/flat_hash_set.h"
#include <string> #include <string>
@ -28,17 +30,32 @@ public:
void addDefinition(absl::string_view id, absl::string_view value); void addDefinition(absl::string_view id, absl::string_view value);
void parseFile(const fs::path& path); 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 getErrorCount() const noexcept { return _errorCount; }
size_t getWarningCount() const noexcept { return _warningCount; } size_t getWarningCount() const noexcept { return _warningCount; }
class Listener { class Listener {
public: public:
virtual void onParseBegin() = 0; // low-level parsing
virtual void onParseEnd() = 0; virtual void onParseBegin() {}
virtual void onParseHeader(const SourceRange& range, const std::string& header) = 0; virtual void onParseEnd() {}
virtual void onParseOpcode(const SourceRange& rangeOpcode, const SourceRange& rangeValue, const std::string& name, const std::string& value) = 0; virtual void onParseHeader(const SourceRange& /*range*/, const std::string& /*header*/) {}
virtual void onParseError(const SourceRange& range, const std::string& message) = 0; virtual void onParseOpcode(const SourceRange& /*rangeOpcode*/, const SourceRange& /*rangeValue*/, const std::string& /*name*/, const std::string& /*value*/) {}
virtual void onParseWarning(const SourceRange& range, const std::string& message) = 0; 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; } void setListener(Listener* listener) noexcept { _listener = listener; }
@ -57,6 +74,9 @@ private:
// recover after error // recover after error
void recover(); void recover();
// state handling
void flushCurrentHeader();
// helpers // helpers
static bool hasComment(Reader& reader); static bool hasComment(Reader& reader);
static size_t skipComment(Reader& reader); static size_t skipComment(Reader& reader);
@ -73,16 +93,19 @@ private:
Listener* _listener = nullptr; Listener* _listener = nullptr;
fs::path _originalDirectory { fs::current_path() }; 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 // a current list of files included, last one at the back
std::vector<std::unique_ptr<Reader>> _included; std::vector<std::unique_ptr<Reader>> _included;
// recursive include guard // recursive include guard
absl::flat_hash_set<std::string> _pathsIncluded; size_t _maxIncludeDepth = 32;
bool _recursiveIncludeGuardEnabled = false;
IncludeFileSet _pathsIncluded;
// parsing state // parsing state
std::string _lastHeader; absl::optional<std::string> _currentHeader;
std::vector<Opcode> _currentOpcodes;
// errors and warnings // errors and warnings
size_t _errorCount = 0; size_t _errorCount = 0;

View file

@ -98,7 +98,8 @@ TEST_CASE("[Files] Subdir include Win")
TEST_CASE("[Files] Recursive include (with include guard)") TEST_CASE("[Files] Recursive include (with include guard)")
{ {
sfz::Synth synth; sfz::Synth synth;
synth.enableRecursiveIncludeGuard(); sfz::Parser& parser = synth.getParser();
parser.setRecursiveIncludeGuardEnabled(true);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sample == "dummy_recursive2.wav"); 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)") TEST_CASE("[Files] Include loops (with include guard)")
{ {
sfz::Synth synth; sfz::Synth synth;
synth.enableRecursiveIncludeGuard(); sfz::Parser& parser = synth.getParser();
parser.setRecursiveIncludeGuardEnabled(true);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sample == "dummy_loop2.wav"); REQUIRE(synth.getRegionView(0)->sample == "dummy_loop2.wav");
@ -502,8 +504,9 @@ TEST_CASE("[Files] Case sentitiveness")
TEST_CASE("[Files] Empty file") TEST_CASE("[Files] Empty file")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::Parser& parser = synth.getParser();
REQUIRE(!synth.loadSfzFile("")); REQUIRE(!synth.loadSfzFile(""));
REQUIRE(synth.getIncludedFiles().empty()); REQUIRE(parser.getIncludedFiles().empty());
REQUIRE(!synth.loadSfzFile({})); REQUIRE(!synth.loadSfzFile({}));
REQUIRE(synth.getIncludedFiles().empty()); REQUIRE(parser.getIncludedFiles().empty());
} }

View file

@ -16,7 +16,7 @@
*/ */
#include "sfizz/Curve.h" #include "sfizz/Curve.h"
#include "sfizz/Parser.h" #include "sfizz/parser/Parser.h"
#include "absl/strings/numbers.h" #include "absl/strings/numbers.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <iostream> #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: public:
explicit CurveParser(sfz::CurveSet& curveSet) explicit CurveParserListener(sfz::CurveSet& curveSet)
: 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") if (header == "curve")
curveSet.addCurveFromHeader(members); curveSet.addCurveFromHeader(members);
@ -73,8 +73,11 @@ int main(int argc, char* argv[])
sfz::CurveSet curveSet = sfz::CurveSet::createPredefined(); sfz::CurveSet curveSet = sfz::CurveSet::createPredefined();
if (!filePath.empty()) { if (!filePath.empty()) {
CurveParser parser(curveSet); sfz::Parser parser;
if (!parser.loadSfzFile(filePath)) { CurveParserListener listener(curveSet);
parser.setListener(&listener);
parser.parseFile(filePath);
if (parser.getErrorCount() > 0) {
std::cerr << "Cannot load SFZ: " << filePath << "\n"; std::cerr << "Cannot load SFZ: " << filePath << "\n";
return 1; return 1;
} }