Support UI in LV2

This commit is contained in:
Jean Pierre Cimalando 2020-08-31 05:47:37 +02:00
parent e2b8b21967
commit b92209de74
17 changed files with 966 additions and 43 deletions

View file

@ -15,7 +15,8 @@ buildenv make DESTDIR=${PWD}/${INSTALL_DIR} install
# Bundle LV2 dependencies
cd "${INSTALL_DIR}"/Library/Audio/Plug-Ins/LV2
dylibbundler -od -b -x sfizz.lv2/sfizz.so -d sfizz.lv2/libs/ -p @loader_path/libs/
dylibbundler -od -b -x sfizz.lv2/Contents/Binary/sfizz.so -d sfizz.lv2/Contents/libs/ -p @loader_path/../libs/
dylibbundler -od -b -x sfizz.lv2/Contents/Binary/sfizz_ui.so -d sfizz.lv2/Contents/libs/ -p @loader_path/../libs/
cd "${TRAVIS_BUILD_DIR}/build"
# Bundle VST3 dependencies

View file

@ -1,6 +1,10 @@
set(VSTGUI_BASEDIR "${CMAKE_CURRENT_SOURCE_DIR}/external/vstgui4")
include("cmake/Vstgui.cmake")
set(EDITOR_RESOURCES
logo.png
PARENT_SCOPE)
# editor
add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL
src/editor/EditIds.h
@ -13,4 +17,5 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL
src/editor/utility/vstgui_after.h
src/editor/utility/vstgui_before.h)
target_include_directories(sfizz_editor PUBLIC "src")
target_link_libraries(sfizz_editor PRIVATE sfizz-vstgui absl::strings)
target_link_libraries(sfizz_editor PRIVATE sfizz-vstgui)
target_link_libraries(sfizz_editor PUBLIC absl::strings absl::variant)

View file

@ -207,3 +207,14 @@ endif()
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
target_compile_definitions(sfizz-vstgui PRIVATE "RELEASE")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(sfizz-vstgui PRIVATE
"-Wno-deprecated-copy"
"-Wno-ignored-qualifiers"
"-Wno-reorder"
"-Wno-sign-compare"
"-Wno-unused-function"
"-Wno-unused-parameter"
"-Wno-unused-variable")
endif()

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -288,7 +288,7 @@ void Editor::Impl::createFrameContents()
view->setBackgroundColor(CColor(0xff, 0xff, 0xff));
SharedPointer<CBitmap> logo = new CBitmap("logo.png");
SharedPointer<CBitmap> logo { new CBitmap("logo.png") };
CRect bottomRow = bounds;
bottomRow.top = bottomRow.bottom - 30;

View file

@ -11,6 +11,10 @@ set (LV2PLUGIN_TTL_SRC_FILES
manifest.ttl.in
${PROJECT_NAME}.ttl.in
)
if (SFIZZ_LV2_UI)
list(APPEND LV2PLUGIN_TTL_SRC_FILES
${PROJECT_NAME}_ui.ttl.in)
endif()
source_group("Turtle Files" FILES
${LV2PLUGIN_TTL_SRC_FILES}
)
@ -19,31 +23,61 @@ add_library (${LV2PLUGIN_PRJ_NAME} MODULE
atomic_compat.h
${LV2PLUGIN_TTL_SRC_FILES})
target_link_libraries (${LV2PLUGIN_PRJ_NAME} ${PROJECT_NAME}::${PROJECT_NAME})
if (SFIZZ_LV2_UI)
add_library (${LV2PLUGIN_PRJ_NAME}_ui MODULE
${PROJECT_NAME}_ui.cpp
vstgui_helpers.h
vstgui_helpers.cpp)
target_link_libraries (${LV2PLUGIN_PRJ_NAME}_ui sfizz_editor sfizz-vstgui)
endif()
# Explicitely strip all symbols on Linux but lv2_descriptor()
# MacOS linker does not support this apparently https://bugs.webkit.org/show_bug.cgi?id=144555
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
file(COPY lv2.version DESTINATION ${CMAKE_BINARY_DIR}/lv2)
target_link_libraries(${LV2PLUGIN_PRJ_NAME} "-Wl,--version-script=lv2.version")
# target_link_libraries(${LV2PLUGIN_PRJ_NAME} "-Wl,-u,lv2_descriptor")
if (SFIZZ_LV2_UI)
file(COPY lv2ui.version DESTINATION ${CMAKE_BINARY_DIR}/lv2)
target_link_libraries(${LV2PLUGIN_PRJ_NAME}_ui "-Wl,--version-script=lv2ui.version")
# target_link_libraries(${LV2PLUGIN_PRJ_NAME}_ui "-Wl,-u,lv2ui_descriptor")
endif()
endif()
target_include_directories(${LV2PLUGIN_PRJ_NAME} PRIVATE . external/ardour)
sfizz_enable_lto_if_needed (${LV2PLUGIN_PRJ_NAME})
if (MINGW)
set_target_properties (${LV2PLUGIN_PRJ_NAME} PROPERTIES LINK_FLAGS "-static")
endif()
if (SFIZZ_LV2_UI)
target_include_directories(${LV2PLUGIN_PRJ_NAME}_ui PRIVATE . external/ardour)
sfizz_enable_lto_if_needed (${LV2PLUGIN_PRJ_NAME}_ui)
if (MINGW)
set_target_properties (${LV2PLUGIN_PRJ_NAME}_ui PROPERTIES LINK_FLAGS "-static")
endif()
endif()
# Remove the "lib" prefix, rename the target name and build it in the .lv build dir
# <build_dir>/lv2/<plugin_name>_lv2.<ext> to
# <build_dir>/lv2/<plugin_name>.lv2/<plugin_name>.<ext>
set_target_properties (${LV2PLUGIN_PRJ_NAME} PROPERTIES PREFIX "")
set_target_properties (${LV2PLUGIN_PRJ_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
set_target_properties (${LV2PLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set_target_properties (${LV2PLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Contents/Binary")
if (SFIZZ_LV2_UI)
set_target_properties (${LV2PLUGIN_PRJ_NAME}_ui PROPERTIES PREFIX "")
set_target_properties (${LV2PLUGIN_PRJ_NAME}_ui PROPERTIES OUTPUT_NAME "${PROJECT_NAME}_ui")
set_target_properties (${LV2PLUGIN_PRJ_NAME}_ui PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Contents/Binary")
endif()
# Generate *.ttl files from *.in sources,
# create the destination directory if it doesn't exists and copy needed files
file (MAKE_DIRECTORY ${PROJECT_BINARY_DIR})
configure_file (manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
configure_file (${PROJECT_NAME}.ttl.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ttl)
if (SFIZZ_LV2_UI)
configure_file (${PROJECT_NAME}_ui.ttl.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_ui.ttl)
endif()
configure_file (LICENSE.md.in ${PROJECT_BINARY_DIR}/LICENSE.md)
if (SFIZZ_USE_VCPKG OR SFIZZ_STATIC_DEPENDENCIES OR CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
file(COPY "lgpl-3.0.txt" DESTINATION ${PROJECT_BINARY_DIR})
@ -54,12 +88,22 @@ set(LV2_RESOURCES
DefaultInstrument.sfz
DefaultScale.scl)
execute_process(
COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/Resources")
COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/Contents/Resources")
foreach(res ${LV2_RESOURCES})
file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/${res}"
DESTINATION "${PROJECT_BINARY_DIR}/Resources")
DESTINATION "${PROJECT_BINARY_DIR}/Contents/Resources")
endforeach()
# Copy editor resources
if (SFIZZ_LV2_UI)
execute_process (
COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/Contents/Resources")
foreach(res ${EDITOR_RESOURCES})
file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources/${res}"
DESTINATION "${PROJECT_BINARY_DIR}/Contents/Resources")
endforeach()
endif()
# Installation
if (NOT MSVC)
install (DIRECTORY ${PROJECT_BINARY_DIR} DESTINATION ${LV2PLUGIN_INSTALL_DIR}

4
lv2/lv2ui.version Normal file
View file

@ -0,0 +1,4 @@
LV2UIABI_1.0 {
global: *lv2ui_descriptor*;
local: *;
};

View file

@ -1,7 +1,13 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
<@LV2PLUGIN_URI@>
a lv2:Plugin ;
lv2:binary <@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
lv2:binary <Contents/Binary/@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
rdfs:seeAlso <@PROJECT_NAME@.ttl> .
@LV2PLUGIN_IF_ENABLE_UI@<@LV2PLUGIN_URI@#ui>
@LV2PLUGIN_IF_ENABLE_UI@ a ui:@LV2_UI_TYPE@ ;
@LV2PLUGIN_IF_ENABLE_UI@ ui:binary <Contents/Binary/@PROJECT_NAME@_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
@LV2PLUGIN_IF_ENABLE_UI@ rdfs:seeAlso <@PROJECT_NAME@_ui.ttl> .

View file

@ -32,6 +32,9 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "sfizz_lv2.h"
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
#include <lv2/atom/util.h>
#include <lv2/buf-size/buf-size.h>
@ -58,23 +61,11 @@
#include "atomic_compat.h"
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
#define SFIZZ_PREFIX SFIZZ_URI "#"
#define SFIZZ__sfzFile SFIZZ_URI ":" "sfzfile"
#define SFIZZ__tuningfile SFIZZ_URI ":" "tuningfile"
#define SFIZZ__numVoices SFIZZ_URI ":" "numvoices"
#define SFIZZ__preloadSize SFIZZ_URI ":" "preload_size"
#define SFIZZ__oversampling SFIZZ_URI ":" "oversampling"
// These ones are just for the worker
#define SFIZZ__logStatus SFIZZ_URI ":" "log_status"
#define SFIZZ__checkModification SFIZZ_URI ":" "check_modification"
#define CHANNEL_MASK 0x0F
#define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK)
#define MIDI_STATUS(byte) (byte & ~CHANNEL_MASK)
#define PITCH_BUILD_AND_CENTER(first_byte, last_byte) (int)(((unsigned int)last_byte << 7) + (unsigned int)first_byte) - 8192
#define MAX_BLOCK_SIZE 8192
#define MAX_PATH_SIZE 1024
#define MAX_VOICES 256
#define DEFAULT_VOICES 64
#define DEFAULT_OVERSAMPLING SFIZZ_OVERSAMPLING_X1
@ -82,8 +73,8 @@
#define LOG_SAMPLE_COUNT 48000
#define UNUSED(x) (void)(x)
#define DEFAULT_SCALA_FILE "Resources/DefaultScale.scl"
#define DEFAULT_SFZ_FILE "Resources/DefaultInstrument.sfz"
#define DEFAULT_SCALA_FILE "Contents/Resources/DefaultScale.scl"
#define DEFAULT_SFZ_FILE "Contents/Resources/DefaultInstrument.sfz"
// This assumes that the longest path is the default sfz file; if not, change it
#define MAX_BUNDLE_PATH_SIZE (MAX_PATH_SIZE - sizeof(DEFAULT_SFZ_FILE))
@ -115,6 +106,11 @@ typedef struct
const float *tuning_frequency_port;
const float *stretch_tuning_port;
float *active_voices_port;
float *num_curves_port;
float *num_masters_port;
float *num_groups_port;
float *num_regions_port;
float *num_samples_port;
// Atom forge
LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread
@ -187,23 +183,6 @@ typedef struct
char bundle_path[MAX_BUNDLE_PATH_SIZE];
} sfizz_plugin_t;
enum
{
SFIZZ_CONTROL = 0,
SFIZZ_NOTIFY = 1,
SFIZZ_LEFT = 2,
SFIZZ_RIGHT = 3,
SFIZZ_VOLUME = 4,
SFIZZ_POLYPHONY = 5,
SFIZZ_OVERSAMPLING = 6,
SFIZZ_PRELOAD = 7,
SFIZZ_FREEWHEELING = 8,
SFIZZ_SCALA_ROOT_KEY = 9,
SFIZZ_TUNING_FREQUENCY = 10,
SFIZZ_STRETCH_TUNING = 11,
SFIZZ_ACTIVE_VOICES = 12,
};
enum
{
SFIZZ_TIMEINFO_POSITION = 1 << 0,
@ -369,6 +348,21 @@ connect_port(LV2_Handle instance,
case SFIZZ_ACTIVE_VOICES:
self->active_voices_port = (float *)data;
break;
case SFIZZ_NUM_CURVES:
self->num_curves_port = (float *)data;
break;
case SFIZZ_NUM_MASTERS:
self->num_masters_port = (float *)data;
break;
case SFIZZ_NUM_GROUPS:
self->num_groups_port = (float *)data;
break;
case SFIZZ_NUM_REGIONS:
self->num_regions_port = (float *)data;
break;
case SFIZZ_NUM_SAMPLES:
self->num_samples_port = (float *)data;
break;
default:
break;
}
@ -977,6 +971,11 @@ run(LV2_Handle instance, uint32_t sample_count)
sfizz_lv2_check_oversampling(self);
sfizz_lv2_check_num_voices(self);
*(self->active_voices_port) = sfizz_get_num_active_voices(self->synth);
*(self->num_curves_port) = sfizz_get_num_curves(self->synth);
*(self->num_masters_port) = sfizz_get_num_masters(self->synth);
*(self->num_groups_port) = sfizz_get_num_groups(self->synth);
*(self->num_regions_port) = sfizz_get_num_regions(self->synth);
*(self->num_samples_port) = sfizz_get_num_preloaded_samples(self->synth);
// Log the buffer usage
self->sample_counter += (int)sample_count;

View file

@ -13,6 +13,7 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix time: <http://lv2plug.in/ns/ext/time#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
@ -83,6 +84,8 @@ midnam:update a lv2:Feature .
opts:supportedOption param:sampleRate ;
opts:supportedOption bufsize:maxBlockLength, bufsize:nominalBlockLength ;
@LV2PLUGIN_IF_ENABLE_UI@ui:ui <@LV2PLUGIN_URI@#ui> ;
patch:writable <@LV2PLUGIN_URI@:sfzfile> ,
<@LV2PLUGIN_URI@:tuningfile> ;
@ -310,4 +313,70 @@ midnam:update a lv2:Feature .
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 256 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 12 ;
lv2:symbol "active_voices" ;
lv2:name "Active voices",
"Voix utilisées"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 256 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 13 ;
lv2:symbol "num_curves" ;
lv2:name "Number of curves",
"Nombre de courbes"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 14 ;
lv2:symbol "num_masters" ;
lv2:name "Number of masters",
"Nombre de maîtres"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 15 ;
lv2:symbol "num_groups" ;
lv2:name "Number of groups",
"Nombre de groupes"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 16 ;
lv2:symbol "num_regions" ;
lv2:name "Number of regions",
"Nombre de regions"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 17 ;
lv2:symbol "num_samples" ;
lv2:name "Number of samples",
"Nombre d'échantillons"@fr ;
pg:group <@LV2PLUGIN_URI@#status> ;
lv2:portProperty lv2:integer ;
lv2:default 0 ;
lv2:minimum 0 ;
lv2:maximum 65535 ;
] .

43
lv2/sfizz_lv2.h Normal file
View file

@ -0,0 +1,43 @@
// 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
#define MAX_PATH_SIZE 1024
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
#define SFIZZ_UI_URI "http://sfztools.github.io/sfizz#ui"
#define SFIZZ_PREFIX SFIZZ_URI "#"
#define SFIZZ__sfzFile SFIZZ_URI ":" "sfzfile"
#define SFIZZ__tuningfile SFIZZ_URI ":" "tuningfile"
#define SFIZZ__numVoices SFIZZ_URI ":" "numvoices"
#define SFIZZ__preloadSize SFIZZ_URI ":" "preload_size"
#define SFIZZ__oversampling SFIZZ_URI ":" "oversampling"
// These ones are just for the worker
#define SFIZZ__logStatus SFIZZ_URI ":" "log_status"
#define SFIZZ__checkModification SFIZZ_URI ":" "check_modification"
enum
{
SFIZZ_CONTROL = 0,
SFIZZ_NOTIFY = 1,
SFIZZ_LEFT = 2,
SFIZZ_RIGHT = 3,
SFIZZ_VOLUME = 4,
SFIZZ_POLYPHONY = 5,
SFIZZ_OVERSAMPLING = 6,
SFIZZ_PRELOAD = 7,
SFIZZ_FREEWHEELING = 8,
SFIZZ_SCALA_ROOT_KEY = 9,
SFIZZ_TUNING_FREQUENCY = 10,
SFIZZ_STRETCH_TUNING = 11,
SFIZZ_ACTIVE_VOICES = 12,
SFIZZ_NUM_CURVES = 13,
SFIZZ_NUM_MASTERS = 14,
SFIZZ_NUM_GROUPS = 15,
SFIZZ_NUM_REGIONS = 16,
SFIZZ_NUM_SAMPLES = 17,
};

492
lv2/sfizz_ui.cpp Normal file
View file

@ -0,0 +1,492 @@
/*
SPDX-License-Identifier: ISC
Sfizz LV2 plugin
Copyright 2019-2020, Paul Ferrand <paul@ferrand.cc>
This file was based on skeleton and example code from the LV2 plugin
distribution available at http://lv2plug.in/
The LV2 sample plugins have the following copyright and notice, which are
extended to the current work:
Copyright 2011-2016 David Robillard <d@drobilla.net>
Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org>
Copyright 2011 James Morris <jwm.art.net@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
Compiling this plugin statically against libsndfile implies distributing it
under the terms of the LGPL v3 license. See the LICENSE.md file for more
information. If you did not receive a LICENSE.md file, inform the current
maintainer.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "sfizz_lv2.h"
#include "editor/Editor.h"
#include "editor/EditorController.h"
#include "editor/EditIds.h"
#include <lv2/ui/ui.h>
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
#include <lv2/atom/util.h>
#include <lv2/midi/midi.h>
#include <lv2/patch/patch.h>
#include <lv2/urid/urid.h>
#include <string>
#include <memory>
#include <cstring>
#include <cstdio>
#include "vstgui_helpers.h"
#include "editor/utility/vstgui_before.h"
#include "vstgui/lib/cframe.h"
#include "vstgui/lib/platform/iplatformframe.h"
#include "editor/utility/vstgui_after.h"
using namespace VSTGUI;
///
struct FrameHolderDeleter {
void operator()(CFrame* frame) const
{
if (frame->getNbReference() != 1)
frame->forget();
else
frame->close();
}
};
typedef std::unique_ptr<CFrame, FrameHolderDeleter> FrameHolder;
///
struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2UI_Write_Function write = nullptr;
LV2UI_Controller con = nullptr;
LV2_URID_Map *map = nullptr;
LV2_URID_Unmap *unmap = nullptr;
LV2UI_Resize *resize = nullptr;
LV2UI_Touch *touch = nullptr;
std::unique_ptr<Editor> editor;
FrameHolder uiFrame;
#if LINUX
SharedPointer<Lv2IdleRunLoop> runLoop;
#endif
/// VSTGUIEditorInterface
CFrame* getFrame() const override { return uiFrame.get(); }
LV2_Atom_Forge atom_forge;
LV2_URID atom_event_transfer_uri;
LV2_URID atom_object_uri;
LV2_URID atom_path_uri;
LV2_URID atom_urid_uri;
LV2_URID midi_event_uri;
LV2_URID patch_get_uri;
LV2_URID patch_set_uri;
LV2_URID patch_property_uri;
LV2_URID patch_value_uri;
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_scala_file_uri;
protected:
void uiSendValue(EditId id, const EditValue& v) override;
void uiBeginSend(EditId id) override;
void uiEndSend(EditId id) override;
void uiSendMIDI(const uint8_t* msg, uint32_t len) override;
private:
void uiTouch(EditId id, bool t);
};
static LV2UI_Handle
instantiate(const LV2UI_Descriptor *descriptor,
const char *plugin_uri,
const char *bundle_path,
LV2UI_Write_Function write_function,
LV2UI_Controller controller,
LV2UI_Widget *widget,
const LV2_Feature * const *features)
{
std::unique_ptr<sfizz_ui_t> self { new sfizz_ui_t };
(void)descriptor;
(void)plugin_uri;
(void)bundle_path;
self->write = write_function;
self->con = controller;
void *parentWindowId = nullptr;
LV2_URID_Map *map = nullptr;
LV2_URID_Unmap *unmap = nullptr;
for (const LV2_Feature *const *f = features; *f; f++)
{
if (!strcmp((**f).URI, LV2_URID__map))
self->map = map = (LV2_URID_Map *)(**f).data;
else if (!strcmp((**f).URI, LV2_URID__unmap))
self->unmap = unmap = (LV2_URID_Unmap *)(**f).data;
else if (!strcmp((**f).URI, LV2_UI__resize))
self->resize = (LV2UI_Resize *)(**f).data;
else if (!strcmp((**f).URI, LV2_UI__touch))
self->touch = (LV2UI_Touch*)(**f).data;
else if (!strcmp((**f).URI, LV2_UI__parent))
parentWindowId = (**f).data;
}
// The map feature is required
if (!map || !unmap)
return nullptr;
LV2_Atom_Forge *forge = &self->atom_forge;
lv2_atom_forge_init(forge, map);
self->atom_event_transfer_uri = map->map(map->handle, LV2_ATOM__eventTransfer);
self->atom_object_uri = map->map(map->handle, LV2_ATOM__Object);
self->atom_path_uri = map->map(map->handle, LV2_ATOM__Path);
self->atom_urid_uri = map->map(map->handle, LV2_ATOM__URID);
self->midi_event_uri = map->map(map->handle, LV2_MIDI__MidiEvent);
self->patch_get_uri = map->map(map->handle, LV2_PATCH__Get);
self->patch_set_uri = map->map(map->handle, LV2_PATCH__Set);
self->patch_property_uri = map->map(map->handle, LV2_PATCH__property);
self->patch_value_uri = map->map(map->handle, LV2_PATCH__value);
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile);
// set up the resource path
// * on Linux, this is determined by going 2 folders back from the SO path
// name, and appending "Contents/Resources" (not overridable)
// * on Windows, the folder is set programmatically
// * on macOS, resource files are looked up using CFBundle APIs
#if _WIN32
IWin32PlatformFrame::setResourceBasePath((std::string(bundle_path) + "\\Contents\\Resources\\").c_str());
#elif __APPLE__
#pragma message("TODO: make resources work on macOS using bundles")
#endif
// makes labels refresh correctly
CView::kDirtyCallAlwaysOnMainThread = true;
const CRect uiBounds(0, 0, Editor::viewWidth, Editor::viewHeight);
CFrame* uiFrame = new CFrame(uiBounds, self.get());
self->uiFrame.reset(uiFrame);
IPlatformFrameConfig* config = nullptr;
#if LINUX
SharedPointer<Lv2IdleRunLoop> runLoop = new Lv2IdleRunLoop;
self->runLoop = runLoop;
VSTGUI::X11::FrameConfig x11Config;
x11Config.runLoop = runLoop;
config = &x11Config;
#endif
if (!uiFrame->open(parentWindowId, kDefaultNative, config))
return nullptr;
Editor *editor = new Editor(*self);
self->editor.reset(editor);
editor->open(*uiFrame);
*widget = reinterpret_cast<LV2UI_Widget>(uiFrame->getPlatformFrame()->getPlatformRepresentation());
if (self->resize)
self->resize->ui_resize(self->resize->handle, Editor::viewWidth, Editor::viewHeight);
// send a request to receive all parameters
uint8_t buffer[256];
lv2_atom_forge_set_buffer(forge, buffer, sizeof(buffer));
LV2_Atom_Forge_Frame frame;
LV2_Atom *msg = (LV2_Atom *)lv2_atom_forge_object(forge, &frame, 0, self->patch_get_uri);
lv2_atom_forge_pop(forge, &frame);
write_function(controller, 0, lv2_atom_total_size(msg), self->atom_event_transfer_uri, msg);
return self.release();
}
static void
cleanup(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
delete self;
}
static void
port_event(LV2UI_Handle ui,
uint32_t port_index,
uint32_t buffer_size,
uint32_t format,
const void *buffer)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
if (format == 0) {
const float v = *reinterpret_cast<const float*>(buffer);
switch (port_index) {
case SFIZZ_VOLUME:
self->uiReceiveValue(EditId::Volume, v);
break;
case SFIZZ_POLYPHONY:
self->uiReceiveValue(EditId::Polyphony, v);
break;
case SFIZZ_OVERSAMPLING:
self->uiReceiveValue(EditId::Oversampling, v);
break;
case SFIZZ_PRELOAD:
self->uiReceiveValue(EditId::PreloadSize, v);
break;
case SFIZZ_SCALA_ROOT_KEY:
self->uiReceiveValue(EditId::ScalaRootKey, v);
break;
case SFIZZ_TUNING_FREQUENCY:
self->uiReceiveValue(EditId::TuningFrequency, v);
break;
case SFIZZ_STRETCH_TUNING:
self->uiReceiveValue(EditId::StretchTuning, v);
break;
case SFIZZ_ACTIVE_VOICES:
self->uiReceiveValue(EditId::UINumActiveVoices, v);
break;
case SFIZZ_NUM_CURVES:
self->uiReceiveValue(EditId::UINumCurves, v);
break;
case SFIZZ_NUM_MASTERS:
self->uiReceiveValue(EditId::UINumMasters, v);
break;
case SFIZZ_NUM_GROUPS:
self->uiReceiveValue(EditId::UINumGroups, v);
break;
case SFIZZ_NUM_REGIONS:
self->uiReceiveValue(EditId::UINumRegions, v);
break;
case SFIZZ_NUM_SAMPLES:
self->uiReceiveValue(EditId::UINumPreloadedSamples, v);
break;
}
}
else if (format == self->atom_event_transfer_uri) {
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
if (atom->type == self->atom_object_uri) {
const LV2_Atom *prop = nullptr;
const LV2_Atom *value = nullptr;
lv2_atom_object_get(
reinterpret_cast<const LV2_Atom_Object *>(atom),
self->patch_property_uri, &prop, self->patch_value_uri, &value, 0);
if (prop && value && prop->type == self->atom_urid_uri) {
const LV2_URID prop_uri = reinterpret_cast<const LV2_Atom_URID *>(prop)->body;
auto *value_body = reinterpret_cast<const char *>(LV2_ATOM_BODY_CONST(value));
if (prop_uri == self->sfizz_sfz_file_uri && value->type == self->atom_path_uri) {
std::string path(value_body, strnlen(value_body, value->size));
self->uiReceiveValue(EditId::SfzFile, path);
}
else if (prop_uri == self->sfizz_scala_file_uri && value->type == self->atom_path_uri) {
std::string path(value_body, strnlen(value_body, value->size));
self->uiReceiveValue(EditId::ScalaFile, path);
}
}
}
}
(void)buffer_size;
}
static int
idle(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
#if LINUX
self->runLoop->execIdle();
#endif
return 0;
}
static const LV2UI_Idle_Interface idle_interface = {
&idle,
};
static int
show(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
self->uiFrame->setVisible(true);
return 0;
}
static int
hide(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
self->uiFrame->setVisible(false);
return 0;
}
static const LV2UI_Show_Interface show_interface = {
&show,
&hide,
};
const void *
extension_data(const char *uri)
{
if (!strcmp(uri, LV2_UI__idleInterface))
return &idle_interface;
if (!strcmp(uri, LV2_UI__showInterface))
return &show_interface;
return nullptr;
}
static const LV2UI_Descriptor descriptor = {
SFIZZ_UI_URI,
instantiate,
cleanup,
port_event,
extension_data,
};
LV2_SYMBOL_EXPORT
const LV2UI_Descriptor *
lv2ui_descriptor(uint32_t index)
{
#if LINUX
VSTGUI::initializeSoHandle();
#endif
switch (index)
{
case 0:
return &descriptor;
default:
return nullptr;
}
}
///
void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v)
{
auto sendFloat = [this](int port, float value) {
write(con, port, sizeof(float), 0, &value);
};
auto sendPath = [this](LV2_URID property, const std::string& value) {
LV2_Atom_Forge *forge = &atom_forge;
LV2_Atom_Forge_Frame frame;
alignas(LV2_Atom) uint8_t buffer[MAX_PATH_SIZE + 512];
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer));
if (lv2_atom_forge_object(forge, &frame, 0, patch_set_uri) &&
lv2_atom_forge_key(forge, patch_property_uri) &&
lv2_atom_forge_urid(forge, property) &&
lv2_atom_forge_key(forge, patch_value_uri) &&
lv2_atom_forge_path(forge, value.data(), value.size()))
{
lv2_atom_forge_pop(forge, &frame);
write(con, SFIZZ_CONTROL, lv2_atom_total_size(atom), atom_event_transfer_uri, atom);
}
};
switch (id) {
case EditId::Volume:
sendFloat(SFIZZ_VOLUME, absl::get<float>(v));
break;
case EditId::Polyphony:
sendFloat(SFIZZ_POLYPHONY, absl::get<float>(v));
break;
case EditId::Oversampling:
sendFloat(SFIZZ_OVERSAMPLING, absl::get<float>(v));
break;
case EditId::PreloadSize:
sendFloat(SFIZZ_PRELOAD, absl::get<float>(v));
break;
case EditId::ScalaRootKey:
sendFloat(SFIZZ_SCALA_ROOT_KEY, absl::get<float>(v));
break;
case EditId::TuningFrequency:
sendFloat(SFIZZ_TUNING_FREQUENCY, absl::get<float>(v));
break;
case EditId::StretchTuning:
sendFloat(SFIZZ_STRETCH_TUNING, absl::get<float>(v));
break;
case EditId::SfzFile:
sendPath(sfizz_sfz_file_uri, absl::get<std::string>(v));
break;
case EditId::ScalaFile:
sendPath(sfizz_scala_file_uri, absl::get<std::string>(v));
break;
default:
break;
}
}
void sfizz_ui_t::uiBeginSend(EditId id)
{
uiTouch(id, true);
}
void sfizz_ui_t::uiEndSend(EditId id)
{
uiTouch(id, false);
}
void sfizz_ui_t::uiTouch(EditId id, bool t)
{
if (!touch)
return;
switch (id) {
case EditId::Volume:
touch->touch(touch->handle, SFIZZ_VOLUME, t);
break;
case EditId::Polyphony:
touch->touch(touch->handle, SFIZZ_POLYPHONY, t);
break;
case EditId::Oversampling:
touch->touch(touch->handle, SFIZZ_OVERSAMPLING, t);
break;
case EditId::PreloadSize:
touch->touch(touch->handle, SFIZZ_PRELOAD, t);
break;
case EditId::ScalaRootKey:
touch->touch(touch->handle, SFIZZ_SCALA_ROOT_KEY, t);
break;
case EditId::TuningFrequency:
touch->touch(touch->handle, SFIZZ_TUNING_FREQUENCY, t);
break;
case EditId::StretchTuning:
touch->touch(touch->handle, SFIZZ_STRETCH_TUNING, t);
break;
default:
break;
}
}
void sfizz_ui_t::uiSendMIDI(const uint8_t* msg, uint32_t len)
{
LV2_Atom_Forge *forge = &atom_forge;
alignas(LV2_Atom) uint8_t buffer[512];
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer));
if (lv2_atom_forge_atom(forge, len, midi_event_uri) &&
lv2_atom_forge_write(forge, msg, len))
{
write(con, SFIZZ_CONTROL, lv2_atom_total_size(atom), atom_event_transfer_uri, atom);
}
}

14
lv2/sfizz_ui.ttl.in Normal file
View file

@ -0,0 +1,14 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
<@LV2PLUGIN_URI@#ui>
lv2:extensionData ui:idleInterface ;
lv2:extensionData ui:showInterface ;
lv2:requiredFeature ui:idleInterface ;
lv2:optionalFeature ui:noUserResize ;
lv2:optionalFeature ui:resize ;
lv2:optionalFeature ui:parent ;
lv2:optionalFeature ui:touch ;
lv2:requiredFeature urid:map ;
lv2:requiredFeature urid:unmap .

170
lv2/vstgui_helpers.cpp Normal file
View file

@ -0,0 +1,170 @@
// 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
// Note(jpc) same code as used in Surge LV2, I am the original author
#include "vstgui_helpers.h"
#include <lv2/ui/ui.h>
#include <algorithm>
#include <memory>
#include <mutex>
#if LINUX
#include <dlfcn.h>
#include <poll.h>
#endif
#if LINUX
void Lv2IdleRunLoop::execIdle()
{
std::chrono::steady_clock::time_point tick = std::chrono::steady_clock::now();
for (Event& ev : _events)
{
if (!ev.alive)
continue;
// TODO LV2: fix me, XCB descriptor polling not working at this point
#if 0
pollfd pfd = {};
pfd.fd = ev.fd;
pfd.events = POLLIN|POLLERR|POLLHUP;
if (poll(&pfd, 1, 0) > 0)
#endif
{
ev.handler->onEvent();
}
}
for (Timer& tm : _timers)
{
if (!tm.alive)
continue;
if (tm.lastTickValid)
{
std::chrono::steady_clock::duration duration = tick - tm.lastTick;
tm.counter += std::chrono::duration_cast<std::chrono::microseconds>(duration);
if (tm.counter >= tm.interval)
{
tm.handler->onTimer();
tm.counter = std::min(tm.counter - tm.interval, tm.interval);
}
}
tm.lastTick = tick;
tm.lastTickValid = true;
}
garbageCollectDeadHandlers<Event>(_events);
garbageCollectDeadHandlers<Timer>(_timers);
}
bool Lv2IdleRunLoop::registerEventHandler(int fd, VSTGUI::X11::IEventHandler* handler)
{
// fprintf(stderr, "registerEventHandler %d %p\n", fd, handler);
Event ev;
ev.fd = fd;
ev.handler = handler;
ev.alive = true;
_events.push_back(ev);
return true;
}
bool Lv2IdleRunLoop::unregisterEventHandler(VSTGUI::X11::IEventHandler* handler)
{
// fprintf(stderr, "unregisterEventHandler %p\n", handler);
auto it = std::find_if(_events.begin(), _events.end(), [handler](const Event& ev) -> bool {
return ev.handler == handler && ev.alive;
});
if (it != _events.end())
it->alive = false;
return true;
}
bool Lv2IdleRunLoop::registerTimer(uint64_t interval, VSTGUI::X11::ITimerHandler* handler)
{
// fprintf(stderr, "registerTimer %lu %p\n", interval, handler);
Timer tm;
tm.interval = std::chrono::milliseconds(interval);
tm.counter = std::chrono::microseconds(0);
tm.lastTickValid = false;
tm.handler = handler;
tm.alive = true;
_timers.push_back(tm);
return true;
}
bool Lv2IdleRunLoop::unregisterTimer(VSTGUI::X11::ITimerHandler* handler)
{
// fprintf(stderr, "unregisterTimer %p\n", handler);
auto it = std::find_if(_timers.begin(), _timers.end(), [handler](const Timer& tm) -> bool {
return tm.handler == handler && tm.alive;
});
if (it != _timers.end())
it->alive = false;
return true;
}
template <class T> void Lv2IdleRunLoop::garbageCollectDeadHandlers(std::list<T>& handlers)
{
auto pos = handlers.begin();
auto end = handlers.end();
while (pos != end)
{
auto curPos = pos++;
if (!curPos->alive)
handlers.erase(curPos);
}
}
#endif
///
#if LINUX
namespace VSTGUI
{
void* soHandle = nullptr;
static volatile bool soHandleInitialized = false;
static std::mutex soHandleMutex;
struct Dl_handle_deleter
{
void operator()(void* x) const noexcept
{
dlclose(x);
}
};
static std::unique_ptr<void, Dl_handle_deleter> soHandlePointer;
} // namespace VSTGUI
void VSTGUI::initializeSoHandle()
{
if (VSTGUI::soHandleInitialized)
return;
std::lock_guard<std::mutex> lock(VSTGUI::soHandleMutex);
if (VSTGUI::soHandleInitialized)
return;
Dl_info info;
if (dladdr((void*)&lv2ui_descriptor, &info))
{
VSTGUI::soHandle = dlopen(info.dli_fname, RTLD_LAZY);
VSTGUI::soHandlePointer.reset(VSTGUI::soHandle);
}
VSTGUI::soHandleInitialized = true;
}
#endif

68
lv2/vstgui_helpers.h Normal file
View file

@ -0,0 +1,68 @@
// 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
// Note(jpc) same code as used in Surge LV2, I am the original author
#pragma once
#include <list>
#include <chrono>
#include "editor/utility/vstgui_before.h"
#include "vstgui/lib/vstguibase.h"
#if LINUX
#include "vstgui/lib/platform/platform_x11.h"
#include "vstgui/lib/platform/linux/x11platform.h"
#endif
#include "editor/utility/vstgui_after.h"
#if LINUX
class Lv2IdleRunLoop : public VSTGUI::X11::IRunLoop
{
public:
void execIdle();
bool registerEventHandler(int fd, VSTGUI::X11::IEventHandler* handler) override;
bool unregisterEventHandler(VSTGUI::X11::IEventHandler* handler) override;
bool registerTimer(uint64_t interval, VSTGUI::X11::ITimerHandler* handler) override;
bool unregisterTimer(VSTGUI::X11::ITimerHandler* handler) override;
void forget() override
{}
void remember() override
{}
private:
struct Event
{
int fd;
VSTGUI::X11::IEventHandler* handler;
bool alive;
};
struct Timer
{
std::chrono::microseconds interval;
std::chrono::microseconds counter;
bool lastTickValid;
std::chrono::steady_clock::time_point lastTick;
VSTGUI::X11::ITimerHandler* handler;
bool alive;
};
private:
template <class T> static void garbageCollectDeadHandlers(std::list<T>& handlers);
private:
std::list<Event> _events;
std::list<Timer> _timers;
};
#endif
#if LINUX
namespace VSTGUI
{
void initializeSoHandle();
}
#endif

View file

@ -27,9 +27,6 @@ set(VSTPLUGIN_HEADERS
SfizzVstState.h
X11RunLoop.h)
set(VSTPLUGIN_RESOURCES
logo.png)
add_library(${VSTPLUGIN_PRJ_NAME} MODULE
${VSTPLUGIN_HEADERS}
${VSTPLUGIN_SOURCES})
@ -71,8 +68,8 @@ endif()
# Create the bundle (see "VST 3 Locations / Format")
execute_process (
COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources")
foreach(res ${VSTPLUGIN_RESOURCES})
file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/${res}"
foreach(res ${EDITOR_RESOURCES})
file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/../editor/resources/${res}"
DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources")
endforeach()
if(WIN32)