Initial importer of DecentSampler

This commit is contained in:
Jean Pierre Cimalando 2021-03-20 17:01:11 +01:00
parent b24d09e7d2
commit ea9b77793d
5 changed files with 273 additions and 3 deletions

View file

@ -1078,6 +1078,8 @@ void Editor::Impl::chooseSfzFile()
fs->addFileExtension(CFileExtension("AIF", "aif"));
fs->addFileExtension(CFileExtension("AIFF", "aiff"));
fs->addFileExtension(CFileExtension("AIFC", "aifc"));
// Decent samples
fs->addFileExtension(CFileExtension("DSPRESET", "dspreset"));
std::string initialDir = getFileChooserInitialDir(currentSfzFile_);
if (!initialDir.empty())

View file

@ -245,11 +245,13 @@ endif()
# Import library
set(SFIZZ_IMPORT_HEADERS
sfizz/import/ForeignInstrument.h
sfizz/import/foreign_instruments/AudioFile.h)
sfizz/import/foreign_instruments/AudioFile.h
sfizz/import/foreign_instruments/DecentSampler.h)
set(SFIZZ_IMPORT_SOURCES
sfizz/import/ForeignInstrument.cpp
sfizz/import/foreign_instruments/AudioFile.cpp)
sfizz/import/foreign_instruments/AudioFile.cpp
sfizz/import/foreign_instruments/DecentSampler.cpp)
add_library(sfizz_import STATIC)
add_library(sfizz::import ALIAS sfizz_import)
@ -258,7 +260,7 @@ target_sources(sfizz_import PRIVATE
target_include_directories(sfizz_import PUBLIC ".")
target_link_libraries(sfizz_import
PUBLIC absl::strings absl::memory sfizz::filesystem
PRIVATE sfizz::pugixml)
PRIVATE sfizz::internal sfizz::pugixml)
# Sfizz spinlock mutex
add_library(sfizz_spin_mutex STATIC

View file

@ -6,12 +6,14 @@
#include "ForeignInstrument.h"
#include "foreign_instruments/AudioFile.h"
#include "foreign_instruments/DecentSampler.h"
namespace sfz {
InstrumentFormatRegistry::InstrumentFormatRegistry()
: formats_ {
&AudioFileInstrumentFormat::getInstance(),
&DecentSamplerInstrumentFormat::getInstance(),
}
{
}

View file

@ -0,0 +1,229 @@
// 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 "DecentSampler.h"
#include "sfizz/Opcode.h"
#include <absl/strings/match.h>
#include <absl/strings/string_view.h>
#include <absl/memory/memory.h>
#include <algorithm>
#include <locale>
#include <sstream>
#include <iostream>
namespace sfz {
DecentSamplerInstrumentFormat& DecentSamplerInstrumentFormat::getInstance()
{
static DecentSamplerInstrumentFormat format;
return format;
}
const char* DecentSamplerInstrumentFormat::name() const noexcept
{
return "DecentSampler instrument";
}
bool DecentSamplerInstrumentFormat::matchesFilePath(const fs::path& path) const
{
const std::string ext = path.extension().u8string();
return absl::EqualsIgnoreCase(ext, ".dspreset");
}
std::unique_ptr<InstrumentImporter> DecentSamplerInstrumentFormat::createImporter() const
{
return absl::make_unique<DecentSamplerInstrumentImporter>();
}
///
std::string DecentSamplerInstrumentImporter::convertToSfz(const fs::path& path) const
{
std::ostringstream os;
os.imbue(std::locale::classic());
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(path.c_str());
if (!result) {
std::cerr << "[sfizz] dspreset: cannot load xml\n";
return {};
}
pugi::xml_node rootNode(doc.child("DecentSampler"));
if (!rootNode) {
std::cerr << "[sfizz] dspreset: missing <DecentSampler> element\n";
return {};
}
pugi::xml_node globalNode(rootNode.child("groups"));
os << "<global>\n";
emitRegionalOpcodes(os, globalNode);
for (pugi::xml_node groupNode: globalNode.children("group"))
{
os << "<group>\n";
emitRegionalOpcodes(os, groupNode);
for (pugi::xml_node sampleNode: groupNode.children("sample"))
{
os << "<region>\n";
emitRegionalOpcodes(os, sampleNode);
}
}
// TODO effects
return os.str();
}
void DecentSamplerInstrumentImporter::emitRegionalOpcodes(std::ostream& os, pugi::xml_node node) const
{
std::vector<Opcode> xmlOpcodes;
xmlOpcodes.reserve(64);
for (pugi::xml_attribute attr : node.attributes())
xmlOpcodes.emplace_back(attr.name(), attr.value());
// a number of opcodes must respect a particular processing order
// sort opcodes by computing a score to each (lower is first)
std::sort(
xmlOpcodes.begin(), xmlOpcodes.end(),
[](const Opcode& a, const Opcode& b) {
auto score = [](const Opcode& x) -> int {
switch (x.lettersOnlyHash) {
case hash("rootNote"):
// rootNote before loNote/hiNote
return -1;
default:
return 0;
}
};
return score(a) < score(b);
});
for (const Opcode& xmlOpcode : xmlOpcodes) {
auto convertToInt = [&os, &xmlOpcode](absl::string_view name) {
int64_t value;
if (readLeadingInt(xmlOpcode.value, &value))
os << name << '=' << value << '\n';
};
auto convertToReal = [&os, &xmlOpcode](absl::string_view name) {
double value;
if (readLeadingFloat(xmlOpcode.value, &value))
os << name << '=' << value << '\n';
};
auto convertToRealEx = [&os, &xmlOpcode](absl::string_view name, double(*conv)(double)) {
double value;
if (readLeadingFloat(xmlOpcode.value, &value))
os << name << '=' << conv(value) << '\n';
};
///
switch (xmlOpcode.lettersOnlyHash) {
case hash("volume"):
{
absl::string_view unit;
double value;
if (readLeadingFloat(xmlOpcode.value, &value, &unit)) {
os << ((unit == "dB") ? "volume" : "amplitude")
<< '=' << value << "\n";
}
}
break;
case hash("ampVeltrack"):
convertToReal("amp_veltrack");
break;
case hash("path"):
os << "sample=" << xmlOpcode.value << '\n';
break;
case hash("rootNote"):
convertToInt("key");
break;
case hash("loNote"):
convertToInt("lokey");
break;
case hash("hiNote"):
convertToInt("hikey");
break;
case hash("loVel"):
convertToInt("lovel");
break;
case hash("hiVel"):
convertToInt("hivel");
break;
case hash("start"):
convertToInt("offset");
break;
case hash("end"):
convertToInt("end");
break;
case hash("tuning"):
convertToReal("transpose");
break;
case hash("pan"):
convertToReal("pan");
break;
case hash("trigger"):
os << "trigger=" << xmlOpcode.value << '\n';
break;
case hash("onLoCC&"):
convertToInt("on_locc" + std::to_string(xmlOpcode.parameters[0]));
break;
case hash("onHiCC&"):
convertToInt("on_hicc" + std::to_string(xmlOpcode.parameters[0]));
break;
case hash("loopStart"):
convertToInt("loop_start");
break;
case hash("loopEnd"):
convertToInt("loop_end");
break;
case hash("loopCrossfade"):
convertToRealEx(
"loop_crossfade",
[](double xfadeInFrames) -> double {
// transform this value to seconds
// TODO: probably should get sample rate from the audio file?
return xfadeInFrames / 44100.0;
});
break;
case hash("loopCrossfadeMode"):
// TODO
break;
case hash("loopEnabled"):
os << "loop_mode="
<< ((xmlOpcode.value == "true") ? "loop_continuous" : "one_shot") << "\n";
break;
case hash("attack"):
convertToReal("ampeg_attack");
break;
case hash("decay"):
convertToReal("ampeg_decay");
break;
case hash("sustain"):
convertToRealEx("ampeg_sustain", [](double x) -> double { return 100.0 * x; });
break;
case hash("release"):
convertToReal("ampeg_release");
break;
case hash("seqMode"):
// TODO
break;
case hash("seqPosition"):
convertToInt("seq_position");
break;
}
}
}
const InstrumentFormat* DecentSamplerInstrumentImporter::getFormat() const noexcept
{
return &DecentSamplerInstrumentFormat::getInstance();
}
} // namespace sfz

View file

@ -0,0 +1,35 @@
// 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 "../ForeignInstrument.h"
#include <pugixml.hpp>
#include <iosfwd>
namespace sfz {
class DecentSamplerInstrumentFormat : public InstrumentFormat {
private:
DecentSamplerInstrumentFormat() noexcept = default;
public:
static DecentSamplerInstrumentFormat& getInstance();
const char* name() const noexcept override;
bool matchesFilePath(const fs::path& path) const override;
std::unique_ptr<InstrumentImporter> createImporter() const override;
};
///
class DecentSamplerInstrumentImporter : public InstrumentImporter {
public:
std::string convertToSfz(const fs::path& path) const override;
const InstrumentFormat* getFormat() const noexcept override;
private:
void emitRegionalOpcodes(std::ostream& os, pugi::xml_node node) const;
};
} // namespace sfz