Skip to content

Commit

Permalink
Initial importer of DecentSampler
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcima committed Mar 21, 2021
1 parent b24d09e commit ea9b777
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 3 deletions.
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

0 comments on commit ea9b777

Please sign in to comment.