Add the basic MIDNAM support for LV2
This commit is contained in:
parent
fb248ca41e
commit
5c509dfd46
9 changed files with 171 additions and 4 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
49
lv2/sfizz.c
49
lv2/sfizz.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
10
src/sfizz.h
10
src/sfizz.h
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue