Add the basic MIDNAM support for LV2

This commit is contained in:
Jean Pierre Cimalando 2020-02-25 11:30:34 +01:00
parent fb248ca41e
commit 5c509dfd46
9 changed files with 171 additions and 4 deletions

View file

@ -48,6 +48,9 @@ else()
target_link_libraries(sfizz-sndfile INTERFACE ${SNDFILE_LIBRARIES})
endif()
add_library(sfizz-pugixml STATIC "src/external/pugixml/src/pugixml.cpp")
target_include_directories(sfizz-pugixml PUBLIC "src/external/pugixml/src")
# If we build with Clang use libc++
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID)
set(USE_LIBCPP ON CACHE BOOL "Use libc++ with clang")

View file

@ -13,7 +13,7 @@ set (LV2PLUGIN_TTL_SRC_FILES
)
add_library (${LV2PLUGIN_PRJ_NAME} SHARED ${PROJECT_NAME}.c ${LV2PLUGIN_TTL_SRC_FILES})
target_link_libraries (${LV2PLUGIN_PRJ_NAME} ${PROJECT_NAME}::${PROJECT_NAME})
target_include_directories(${LV2PLUGIN_PRJ_NAME} PRIVATE .)
target_include_directories(${LV2PLUGIN_PRJ_NAME} PRIVATE . external/ardour)
sfizz_enable_lto_if_needed (${LV2PLUGIN_PRJ_NAME})
# Remove the "lib" prefix, rename the target name and build it in the .lv build dir

View file

@ -47,10 +47,13 @@
#include <lv2/log/logger.h>
#include <lv2/log/log.h>
#include <ardour/lv2_extensions.h>
#include <math.h>
#include <sfizz.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <string.h>
#define DEFAULT_SFZ_FILE "/home/paul/Documents/AVL_Percussions/AVL_Drumkits_Percussion-1.0-Alt.sfz"
@ -90,6 +93,7 @@ typedef struct
LV2_URID_Unmap *unmap;
LV2_Worker_Schedule *worker;
LV2_Log_Log *log;
LV2_Midnam *midnam;
// Ports
const LV2_Atom_Sequence *control_port;
@ -145,6 +149,7 @@ typedef struct
int max_block_size;
int sample_counter;
float sample_rate;
_Atomic(bool) must_update_midnam;
} sfizz_plugin_t;
enum
@ -310,6 +315,9 @@ instantiate(const LV2_Descriptor *descriptor,
if (!strcmp((**f).URI, LV2_LOG__log))
self->log = (**f).data;
if (!strcmp((**f).URI, LV2_MIDNAM__update))
self->midnam = (**f).data;
}
// Setup the loggers
@ -684,6 +692,11 @@ run(LV2_Handle instance, uint32_t sample_count)
// Render the block
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
if (self->midnam && atomic_exchange(&self->must_update_midnam, false))
{
self->midnam->update(self->midnam->handle);
}
}
static uint32_t
@ -770,6 +783,8 @@ sfizz_lv2_update_file_info(sfizz_plugin_t* self, const char* file_path)
lv2_log_note(&self->logger, "[sfizz] Number of masters: %d\n", sfizz_get_num_masters(self->synth));
lv2_log_note(&self->logger, "[sfizz] Number of groups: %d\n", sfizz_get_num_groups(self->synth));
lv2_log_note(&self->logger, "[sfizz] Number of regions: %d\n", sfizz_get_num_regions(self->synth));
atomic_store(&self->must_update_midnam, true);
}
static LV2_State_Status
@ -1066,12 +1081,44 @@ work_response(LV2_Handle instance,
return LV2_WORKER_SUCCESS;
}
static char *
midnam_model(LV2_Handle instance)
{
char *model = malloc(64);
if (!model)
return NULL;
sprintf(model, "Sfizz LV2:%p", instance);
return model;
}
static char *
midnam_export(LV2_Handle instance)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
char *model = midnam_model(instance);
if (!model)
return NULL;
char *xml = sfizz_export_midnam(self->synth, model);
free(model);
return xml;
}
static void
midnam_free(char *string)
{
free(string);
}
static const void *
extension_data(const char *uri)
{
static const LV2_Options_Interface options = {lv2_get_options, lv2_set_options};
static const LV2_State_Interface state = {save, restore};
static const LV2_Worker_Interface worker = {work, work_response, NULL};
static const LV2_Midnam_Interface midnam = {midnam_export, midnam_model, midnam_free};
// Advertise the extensions we support
if (!strcmp(uri, LV2_OPTIONS__interface))
@ -1080,6 +1127,8 @@ extension_data(const char *uri)
return &state;
else if (!strcmp(uri, LV2_WORKER__interface))
return &worker;
else if (!strcmp(uri, LV2_MIDNAM__interface))
return &midnam;
return NULL;
}

View file

@ -5,7 +5,7 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix opts: <http://lv2plug.in/ns/ext/options#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
@prefix pprop: <http://lv2plug.in/ns/ext/port-props#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@ -14,6 +14,10 @@
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@prefix midnam: <http://ardour.org/lv2/midnam#> .
midnam:interface a lv2:ExtensionData .
midnam:update a lv2:Feature .
<#config>
a pg:Group ;
@ -65,6 +69,9 @@
lv2:optionalFeature lv2:hardRTCapable, opts:options ;
lv2:extensionData opts:interface, state:interface, work:interface ;
lv2:optionalFeature midnam:update ;
lv2:extensionData midnam:interface ;
patch:writable <@LV2PLUGIN_URI@:sfzfile> ;
lv2:port [

View file

@ -30,7 +30,7 @@ target_sources(sfizz_static PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp)
target_include_directories (sfizz_static PUBLIC .)
target_include_directories (sfizz_static PUBLIC external)
target_link_libraries (sfizz_static PUBLIC absl::strings absl::span)
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile)
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml)
add_library (sfizz::parser ALIAS sfizz_parser)
add_library (sfizz::sfizz ALIAS sfizz_static)
@ -47,7 +47,7 @@ if (SFIZZ_SHARED)
target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp)
target_include_directories (sfizz_shared PRIVATE .)
target_include_directories (sfizz_static PRIVATE external)
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile)
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml)
target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS)
set_target_properties (sfizz_shared PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
set_property (TARGET sfizz_shared PROPERTY SOVERSION ${PROJECT_VERSION_MAJOR})

View file

@ -94,6 +94,16 @@ SFIZZ_EXPORTED_API int sfizz_get_num_masters(sfizz_synth_t* synth);
* @return int the number of curves
*/
SFIZZ_EXPORTED_API int sfizz_get_num_curves(sfizz_synth_t* synth);
/**
* @brief Export a MIDI Name document describing the the currently loaded
* SFZ file.
*
* @param synth The synth
* @param model the model name used if a non-empty string, otherwise generated
*
* @return char* a newly allocated XML string, which must be freed after use
*/
SFIZZ_EXPORTED_API char* sfizz_export_midnam(sfizz_synth_t* synth, const char* model);
/**
* @brief Returns the number of preloaded samples for the current SFZ file.
*

View file

@ -11,6 +11,7 @@
#include "MidiState.h"
#include "ScopedFTZ.h"
#include "StringViewHelpers.h"
#include "pugixml.hpp"
#include "absl/algorithm/container.h"
#include "absl/strings/str_replace.h"
#include <algorithm>
@ -577,6 +578,94 @@ int sfz::Synth::getNumCurves() const noexcept
return numCurves;
}
std::string sfz::Synth::exportMidnam(absl::string_view model) const
{
pugi::xml_document doc;
// TODO: perhaps define this somewhere, or use some metadata from the SFZ
const char *manufacturer = "Paul Ferrand";
const char *defaultModel = "Sfizz";
if (model.empty())
model = defaultModel;
doc.append_child(pugi::node_doctype).set_value(
"MIDINameDocument PUBLIC"
" \"-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN\""
" \"http://www.midi.org/dtds/MIDINameDocument10.dtd\"");
pugi::xml_node root = doc.append_child("MIDINameDocument");
root.append_child(pugi::node_comment)
.set_value("Generated by Sfizz for the current instrument");
root.append_child("Author");
pugi::xml_node device = root.append_child("MasterDeviceNames");
device.append_child("Manufacturer")
.append_child(pugi::node_pcdata).set_value(manufacturer);
device.append_child("Model")
.append_child(pugi::node_pcdata).set_value(std::string(model).c_str());
{
pugi::xml_node devmode = device.append_child("CustomDeviceMode");
devmode.append_attribute("Name").set_value("Default");
pugi::xml_node nsas = devmode.append_child("ChannelNameSetAssignments");
for (unsigned c = 0; c < 16; ++c) {
pugi::xml_node nsa = nsas.append_child("ChannelNameSetAssign");
nsa.append_attribute("Channel").set_value(std::to_string(c + 1).c_str());
nsa.append_attribute("NameSet").set_value("Play");
}
}
{
pugi::xml_node chns = device.append_child("ChannelNameSet");
chns.append_attribute("Name").set_value("Play");
pugi::xml_node acs = chns.append_child("AvailableForChannels");
for (unsigned c = 0; c < 16; ++c) {
pugi::xml_node ac = acs.append_child("AvailableChannel");
ac.append_attribute("Channel").set_value(std::to_string(c + 1).c_str());
ac.append_attribute("Available").set_value("true");
}
chns.append_child("UsesControlNameList")
.append_attribute("Name").set_value("Controls");
}
{
pugi::xml_node cns = device.append_child("ControlNameList");
cns.append_attribute("Name").set_value("Controls");
for (const CCNamePair& pair : ccNames) {
pugi::xml_node cn = cns.append_child("Control");
cn.append_attribute("Type").set_value("7bit");
cn.append_attribute("Number").set_value(std::to_string(pair.first).c_str());
cn.append_attribute("Name").set_value(pair.second.c_str());
}
}
///
struct string_writer : pugi::xml_writer {
std::string result;
string_writer()
{
result.reserve(8192);
}
void write(const void* data, size_t size) override
{
result.append(static_cast<const char*>(data), size);
}
};
///
string_writer writer;
doc.save(writer);
return std::move(writer.result);
}
const sfz::Region* sfz::Synth::getRegionView(int idx) const noexcept
{
return (size_t)idx < regions.size() ? regions[idx].get() : nullptr;

View file

@ -111,6 +111,10 @@ public:
* @return int
*/
int getNumCurves() const noexcept;
/**
* @brief Export a MIDI Name document describing the loaded instrument
*/
std::string exportMidnam(absl::string_view model = {}) const;
/**
* @brief Get a raw view into a specific region. This is mostly used
* for testing.

View file

@ -49,6 +49,11 @@ int sfizz_get_num_curves(sfizz_synth_t* synth)
auto self = reinterpret_cast<sfz::Synth*>(synth);
return self->getNumCurves();
}
char* sfizz_export_midnam(sfizz_synth_t* synth, const char* model)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return strdup(self->exportMidnam(model ? model : "").c_str());
}
size_t sfizz_get_num_preloaded_samples(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);