Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial importer of DecentSampler #725

Merged
merged 1 commit into from
Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/editor/src/editor/Editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
8 changes: 5 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/import/ForeignInstrument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
{
}
Expand Down
229 changes: 229 additions & 0 deletions src/sfizz/import/foreign_instruments/DecentSampler.cpp
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions src/sfizz/import/foreign_instruments/DecentSampler.h
Original file line number Diff line number Diff line change
@@ -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