@@ -103,7 +103,7 @@
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [ "/etc/pipedal/config", "/etc/pipedal/react", "-port", "0.0.0.0:8080", "-log-level","debug" ],
|
||||
"args": [ "/etc/pipedal/config", "${workspaceFolder}/vite/dist", "-port", "0.0.0.0:8080", "-log-level","debug" ],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
@@ -142,7 +142,8 @@
|
||||
//"[pipedal_alsa_test]"
|
||||
// "[wifi_channels_test]"
|
||||
// "[locale]"
|
||||
"[pipewire_input_stream]"
|
||||
//"[mod_gui_init]"
|
||||
"[mod_gui_templates]"
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
"Chowdhury",
|
||||
"Chowdhury's",
|
||||
"discursus",
|
||||
"distro",
|
||||
"distros",
|
||||
"dpkg",
|
||||
"flac",
|
||||
@@ -134,6 +135,7 @@
|
||||
"midiclip",
|
||||
"midisong",
|
||||
"mlmodel",
|
||||
"modui",
|
||||
"MOTU",
|
||||
"Netplan",
|
||||
"Pedalboard",
|
||||
@@ -161,7 +163,7 @@
|
||||
"cSpell.ignoreWords": [
|
||||
"nammodel"
|
||||
],
|
||||
"cSpell.enabled": true,
|
||||
"cSpell.enabled": false,
|
||||
|
||||
// Disable all automatic completion suggestions - only show when Ctrl+Space is pressed
|
||||
"editor.quickSuggestions": {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.4.77
|
||||
VERSION 1.4.79
|
||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||
)
|
||||
|
||||
EXECUTE_PROCESS( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
|
||||
|
||||
set (DISPLAY_VERSION "PiPedal v1.4.77-Beta")
|
||||
set (DISPLAY_VERSION "PiPedal v1.4.79-Beta")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
@@ -96,13 +96,17 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi"
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, authbind, curl, gpg, alsa-base| pipewire, alsa-utils" )
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils" )
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
|
||||
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
set (CPACK_STRIP_FILES true)
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set (CPACK_STRIP_FILES false)
|
||||
else()
|
||||
set (CPACK_STRIP_FILES true)
|
||||
endif()
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_SIGN_ALGORITHM "detached")
|
||||
set(CPACK_DEBIAN_PACKAGE_SIGN_TYPE "origin")
|
||||
|
||||
@@ -319,6 +319,7 @@ namespace pipedal
|
||||
}
|
||||
if (seqHandle)
|
||||
{
|
||||
Lv2Log::debug("Closing ALSA Sequencer");
|
||||
snd_seq_close(seqHandle);
|
||||
seqHandle = nullptr;
|
||||
}
|
||||
@@ -608,7 +609,7 @@ namespace pipedal
|
||||
#ifndef NDEBUG
|
||||
#define MSG_DEBUG_LOG(x) \
|
||||
case x: \
|
||||
Lv2Log::debug("ALSA Sequencer Message" #x); \
|
||||
Lv2Log::debug("ALSA Sequencer Message " #x); \
|
||||
break;
|
||||
#else
|
||||
#define MSG_DEBUG_LOG(x)
|
||||
|
||||
@@ -73,6 +73,7 @@ DBusDispatcher::PostHandle DBusDispatcher::PostDelayed(const clock::duration &de
|
||||
void DBusDispatcher::Run()
|
||||
{
|
||||
this->eventFd = eventfd(0, 0);
|
||||
threadStarted = true;
|
||||
this->serviceThread = std::thread(
|
||||
[this]()
|
||||
{ this->ThreadProc(); });
|
||||
@@ -169,7 +170,7 @@ void DBusDispatcher::ThreadProc()
|
||||
int eventTimeout = GetEventTimeoutMs();
|
||||
if (eventTimeout != -1 && eventTimeout < pollTimeout)
|
||||
{
|
||||
pollTimeout = eventTimeout;/*+-*/
|
||||
pollTimeout = eventTimeout; /*+-*/
|
||||
}
|
||||
if (pollTimeout > 250 || pollTimeout == -1)
|
||||
{
|
||||
@@ -197,13 +198,13 @@ void DBusDispatcher::ThreadProc()
|
||||
LogTrace("DBusDispatcher", "ThreadProc", "Service thread terminated.");
|
||||
}
|
||||
|
||||
void DBusDispatcher::Wait()
|
||||
void DBusDispatcher::WaitForClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!threadJoined)
|
||||
if (threadStarted)
|
||||
{
|
||||
threadJoined = true;
|
||||
threadStarted = false;
|
||||
serviceThread.join();
|
||||
}
|
||||
}
|
||||
@@ -229,7 +230,7 @@ void DBusDispatcher::Stop()
|
||||
|
||||
this->stopping = true;
|
||||
WakeThread();
|
||||
Wait();
|
||||
WaitForClose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,22 +24,20 @@
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
;
|
||||
static const char*INVARIANT_WEEK_DAYS[] =
|
||||
{ "Sun","Mon", "Tue", "Wed","Thu", "Fri","Sat"};
|
||||
static const char *INVARIANT_WEEK_DAYS[] =
|
||||
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
static const char*INVARIANT_MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep","Oct","Nov", "Dec"};
|
||||
static const char *INVARIANT_MONTHS[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
static const char*FORMAT = "%s, %02d %s %04d %02d:%02d:%02d GMT";
|
||||
static const char *FORMAT = "%s, %02d %s %04d %02d:%02d:%02d GMT";
|
||||
|
||||
std::string HtmlHelper::timeToHttpDate()
|
||||
{
|
||||
return timeToHttpDate(time(nullptr));
|
||||
}
|
||||
|
||||
|
||||
std::string HtmlHelper::timeToHttpDate(std::filesystem::file_time_type time)
|
||||
{
|
||||
|
||||
@@ -56,29 +54,30 @@ std::string HtmlHelper::timeToHttpDate(time_t time)
|
||||
|
||||
struct tm gmTm;
|
||||
|
||||
if (gmtime_r(&time,&gmTm) == nullptr)
|
||||
if (gmtime_r(&time, &gmTm) == nullptr)
|
||||
{
|
||||
return "Mon, 01 Jan 1970 00:00:00 GMT";
|
||||
}
|
||||
char szBuffer[sizeof("Mon, 01 Jan 1970 00:00:00 GMT+")];
|
||||
snprintf(szBuffer,sizeof(szBuffer), FORMAT,
|
||||
INVARIANT_WEEK_DAYS[gmTm.tm_wday],
|
||||
gmTm.tm_mday,
|
||||
INVARIANT_MONTHS[gmTm.tm_mon],
|
||||
gmTm.tm_year+1900,
|
||||
gmTm.tm_hour,
|
||||
gmTm.tm_min,
|
||||
gmTm.tm_sec);
|
||||
snprintf(szBuffer, sizeof(szBuffer), FORMAT,
|
||||
INVARIANT_WEEK_DAYS[gmTm.tm_wday],
|
||||
gmTm.tm_mday,
|
||||
INVARIANT_MONTHS[gmTm.tm_mon],
|
||||
gmTm.tm_year + 1900,
|
||||
gmTm.tm_hour,
|
||||
gmTm.tm_min,
|
||||
gmTm.tm_sec);
|
||||
return szBuffer;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int hexN(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c-'0';
|
||||
if (c >= 'a' && c <= 'f') return 10+c-'a';
|
||||
if (c >= 'A' && c <= 'F') return 10+c-'A';
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return 10 + c - 'a';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return 10 + c - 'A';
|
||||
throw std::invalid_argument("Malformed URL.");
|
||||
}
|
||||
static char hexChar(char c1, char c2)
|
||||
@@ -86,15 +85,19 @@ static char hexChar(char c1, char c2)
|
||||
return (char)((hexN(c1) << 4) + hexN(c2));
|
||||
}
|
||||
|
||||
class SafeCharacterMap {
|
||||
class SafeCharacterMap
|
||||
{
|
||||
bool safeCharacter[256];
|
||||
|
||||
public:
|
||||
SafeCharacterMap()
|
||||
SafeCharacterMap()
|
||||
{
|
||||
for (int i = 0; i < 0x20; ++i) {
|
||||
for (int i = 0; i < 0x20; ++i)
|
||||
{
|
||||
safeCharacter[i] = false;
|
||||
}
|
||||
for (int i = 0x20; i < 0x80; ++i) {
|
||||
for (int i = 0x20; i < 0x80; ++i)
|
||||
{
|
||||
safeCharacter[i] = true;
|
||||
}
|
||||
for (int i = 0x80; i < 256; ++i)
|
||||
@@ -102,27 +105,25 @@ public:
|
||||
safeCharacter[i] = false;
|
||||
}
|
||||
const char *unsafeCharacters = ":/+?=#%@ ";
|
||||
for (const char*p = unsafeCharacters; *p != 0; ++p)
|
||||
for (const char *p = unsafeCharacters; *p != 0; ++p)
|
||||
{
|
||||
safeCharacter[(uint8_t)*p] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool isSafeCharacter(char c) { return safeCharacter[(uint8_t)c];}
|
||||
bool isSafeCharacter(char c) { return safeCharacter[(uint8_t)c]; }
|
||||
} g_safeCharacterMap;
|
||||
|
||||
|
||||
std::string HtmlHelper::encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment )
|
||||
std::string HtmlHelper::encode_url_segment(const char *pStart, const char *pEnd, bool isQuerySegment)
|
||||
{
|
||||
std::stringstream s;
|
||||
encode_url_segment(s,pStart,pEnd,isQuerySegment);
|
||||
encode_url_segment(s, pStart, pEnd, isQuerySegment);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static char hexChars[] = "0123456789ABCDEF";
|
||||
|
||||
void HtmlHelper::encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment)
|
||||
void HtmlHelper::encode_url_segment(std::ostream &os, const char *pStart, const char *pEnd, bool isQuerySegment)
|
||||
{
|
||||
while (pStart != pEnd)
|
||||
{
|
||||
@@ -130,21 +131,21 @@ void HtmlHelper::encode_url_segment(std::ostream&os, const char*pStart, const ch
|
||||
if (g_safeCharacterMap.isSafeCharacter(c))
|
||||
{
|
||||
os << c;
|
||||
} else if (c == ' ' && isQuerySegment)
|
||||
}
|
||||
else if (c == ' ' && isQuerySegment)
|
||||
{
|
||||
os << '+';
|
||||
|
||||
os << c;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
os << '%' << hexChars[(c >> 4) & 0x0f] << hexChars[c & 0x0F];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, bool isQuery)
|
||||
std::string HtmlHelper::decode_url_segment(const char *pStart, const char *pEnd, bool isQuery)
|
||||
{
|
||||
std::stringstream s;
|
||||
auto p = pStart;
|
||||
@@ -155,16 +156,23 @@ std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, b
|
||||
if (c == '+')
|
||||
{
|
||||
s << ' ';
|
||||
} else if (c != '%')
|
||||
}
|
||||
else if (c != '%')
|
||||
{
|
||||
s << c;
|
||||
} else {
|
||||
if (pStart+2 < pEnd)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pStart + 2 < pEnd)
|
||||
{
|
||||
char c1 = *p;
|
||||
++p;
|
||||
char c2 = *p;
|
||||
++p;
|
||||
s << hexChar(c1, c2);
|
||||
}
|
||||
else
|
||||
{
|
||||
char c1 = *p; ++p;
|
||||
char c2 = *p; ++p;
|
||||
s << hexChar(c1,c2);
|
||||
} else {
|
||||
throw std::invalid_argument("Malformed URL.");
|
||||
}
|
||||
}
|
||||
@@ -172,9 +180,9 @@ std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, b
|
||||
return s.str();
|
||||
}
|
||||
|
||||
std::string HtmlHelper::decode_url_segment(const char*text, bool isQuery)
|
||||
std::string HtmlHelper::decode_url_segment(const char *text, bool isQuery)
|
||||
{
|
||||
return decode_url_segment(text, text + strlen(text),isQuery);
|
||||
return decode_url_segment(text, text + strlen(text), isQuery);
|
||||
}
|
||||
|
||||
void HtmlHelper::utf32_to_utf8_stream(std::ostream &s, uint32_t uc)
|
||||
@@ -182,20 +190,27 @@ void HtmlHelper::utf32_to_utf8_stream(std::ostream &s, uint32_t uc)
|
||||
if (uc < 0x80u)
|
||||
{
|
||||
s << (char)uc;
|
||||
} else if (uc < 0x800u) {
|
||||
}
|
||||
else if (uc < 0x800u)
|
||||
{
|
||||
s << (char)(0xC0 + (uc >> 6));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
|
||||
} else if (uc < 0x10000u) {
|
||||
}
|
||||
else if (uc < 0x10000u)
|
||||
{
|
||||
s << (char)(0xE0 + (uc >> 12));
|
||||
s << (char)(0x80 + ((uc >> 6) & 0x3F));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
} else if (uc < 0x0110000) {
|
||||
}
|
||||
else if (uc < 0x0110000)
|
||||
{
|
||||
s << (char)(0xF0 + (uc >> 18));
|
||||
s << (char)(0x80 + ((uc >> 12) & 0x3F));
|
||||
s << (char)(0x80 + ((uc >> 6) & 0x3F));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::range_error("Illegal UTF-32 character.");
|
||||
}
|
||||
}
|
||||
@@ -205,20 +220,22 @@ const std::string ESPECIALS = "()<>@,;:\"/[]?.=";
|
||||
#define MAX_FILENAME_LENGTH 96
|
||||
#define MAX_SEGMENT_LENGTH 74 // 75 per RFC2047.2. But leave space for a ';' to allow easier breaking of lines.
|
||||
|
||||
std::string HtmlHelper::Rfc5987EncodeFileName(const std::string&name)
|
||||
std::string HtmlHelper::Rfc5987EncodeFileName(const std::string &name)
|
||||
{
|
||||
// Encode all characters. Let the browser handle safe-ifying the actual name, since that's os dependent.
|
||||
std::stringstream s;
|
||||
std::string prefix = "UTF-8''";
|
||||
std::string postfix = "";
|
||||
s << prefix;
|
||||
for (char c: name)
|
||||
for (char c : name)
|
||||
{
|
||||
uint8_t uc = (uint8_t)c;
|
||||
if (uc < 0x20 || uc >= 0x80 || ESPECIALS.find(c) != std::string::npos)
|
||||
{
|
||||
s << '%' << hexChars[uc >> 4] << hexChars[uc & 0x0F];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
s << c;
|
||||
}
|
||||
}
|
||||
@@ -226,7 +243,6 @@ std::string HtmlHelper::Rfc5987EncodeFileName(const std::string&name)
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
||||
#define MAX_FILE_NAME_LENGHT 96
|
||||
|
||||
const std::string SF_SPECIALS = " <>@;:\"\'/[]?=";
|
||||
@@ -249,23 +265,126 @@ std::string HtmlHelper::SafeFileName(const std::string &name)
|
||||
return s.str();
|
||||
}
|
||||
|
||||
std::string HtmlHelper::HtmlEncode(const std::string& text)
|
||||
std::string HtmlHelper::HtmlEncode(const std::string &text)
|
||||
{
|
||||
std::stringstream os;
|
||||
for (char c: text)
|
||||
for (char c : text)
|
||||
{
|
||||
switch(c)
|
||||
switch (c)
|
||||
{
|
||||
case '<': os << "<"; break;
|
||||
case '>': os << ">"; break;
|
||||
case '&': os << "&"; break;
|
||||
default: os << c; break;
|
||||
|
||||
case '<':
|
||||
os << "<";
|
||||
break;
|
||||
case '>':
|
||||
os << ">";
|
||||
break;
|
||||
case '&':
|
||||
os << "&";
|
||||
break;
|
||||
default:
|
||||
os << c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
static const uint64_t crc64_table[256] = {
|
||||
0x0000000000000000ULL, 0x42F0E1EBA9EA3693ULL, 0x85E1C3D753D46D26ULL, 0xC711223CFA3E5BB5ULL,
|
||||
0x493366450E42ECDFULL, 0x0BC387AEA7A8DA4CULL, 0xCCD2A5925D9681F9ULL, 0x8E224479F47CB76AULL,
|
||||
0x9266CC8A1C85D9BEULL, 0xD0962D61B56FEF2DULL, 0x17870F5D4F51B498ULL, 0x5577EEB6E6BB820BULL,
|
||||
0xDB55AACF12C73561ULL, 0x99A54B24BB2D03F2ULL, 0x5EB4691841135847ULL, 0x1C4488F3E8F96ED4ULL,
|
||||
0x663D78FF90E185EFULL, 0x24CD9914390BB37CULL, 0xE3DCBB28C335E8C9ULL, 0xA12C5AC36ADFDE5AULL,
|
||||
0x2F0E1EBA9EA36930ULL, 0x6DFEFF5137495FA3ULL, 0xAAEFDD6DCD770416ULL, 0xE81F3C86649D3285ULL,
|
||||
0xF45BB4758C645C51ULL, 0xB6AB559E258E6AC2ULL, 0x71BA77A2DFB03177ULL, 0x334A9649765A07E4ULL,
|
||||
0xBD68D2308226B08EULL, 0xFF9833DB2BCC861DULL, 0x388911E7D1F2DDA8ULL, 0x7A79F00C7818EB3BULL,
|
||||
0xCC7AF1FF21C30BDEULL, 0x8E8A101488293D4DULL, 0x499B3228721766F8ULL, 0x0B6BD3C3DBFD506BULL,
|
||||
0x854997BA2F81E701ULL, 0xC7B97651866BD192ULL, 0x00A8546D7C558A27ULL, 0x4258B586D5BFBCB4ULL,
|
||||
0x5E1C3D753D46D260ULL, 0x1CECDC9E94ACE4F3ULL, 0xDBFDFEA26E92BF46ULL, 0x990D1F49C77889D5ULL,
|
||||
0x172F5B3033043EBFULL, 0x55DFBADB9AEE082CULL, 0x92CE98E760D05399ULL, 0xD03E790CC93A650AULL,
|
||||
0xAA478900B1228E31ULL, 0xE8B768EB18C8B8A2ULL, 0x2FA64AD7E2F6E317ULL, 0x6D56AB3C4B1CD584ULL,
|
||||
0xE374EF45BF6062EEULL, 0xA1840EAE168A547DULL, 0x66952C92ECB40FC8ULL, 0x2465CD79455E395BULL,
|
||||
0x3821458AADA7578FULL, 0x7AD1A461044D611CULL, 0xBDC0865DFE733AA9ULL, 0xFF3067B657990C3AULL,
|
||||
0x711223CFA3E5BB50ULL, 0x33E2C2240A0F8DC3ULL, 0xF4F3E018F031D676ULL, 0xB60301F359DBE0E5ULL,
|
||||
0xDA050215EA6C212FULL, 0x98F5E3FE438617BCULL, 0x5FE4C1C2B9B84C09ULL, 0x1D14202910527A9AULL,
|
||||
0x93366450E42ECDF0ULL, 0xD1C685BB4DC4FB63ULL, 0x16D7A787B7FAA0D6ULL, 0x5427466C1E109645ULL,
|
||||
0x4863CE9FF6E9F891ULL, 0x0A932F745F03CE02ULL, 0xCD820D48A53D95B7ULL, 0x8F72ECA30CD7A324ULL,
|
||||
0x0150A8DAF8AB144EULL, 0x43A04931514122DDULL, 0x84B16B0DAB7F7968ULL, 0xC6418AE602954FFBULL,
|
||||
0xBC387AEA7A8DA4C0ULL, 0xFEC89B01D3679253ULL, 0x39D9B93D2959C9E6ULL, 0x7B2958D680B3FF75ULL,
|
||||
0xF50B1CAF74CF481FULL, 0xB7FBFD44DD257E8CULL, 0x70EADF78271B2539ULL, 0x321A3E938EF113AAULL,
|
||||
0x2E5EB66066087D7EULL, 0x6CAE578BCFE24BEDULL, 0xABBF75B735DC1058ULL, 0xE94F945C9C3626CBULL,
|
||||
0x676DD025684A91A1ULL, 0x259D31CEC1A0A732ULL, 0xE28C13F23B9EFC87ULL, 0xA07CF2199274CA14ULL,
|
||||
0x167FF3EACBAF2AF1ULL, 0x548F120162451C62ULL, 0x939E303D987B47D7ULL, 0xD16ED1D631917144ULL,
|
||||
0x5F4C95AFC5EDC62EULL, 0x1DBC74446C07F0BDULL, 0xDAAD56789639AB08ULL, 0x985DB7933FD39D9BULL,
|
||||
0x84193F60D72AF34FULL, 0xC6E9DE8B7EC0C5DCULL, 0x01F8FCB784FE9E69ULL, 0x43081D5C2D14A8FAULL,
|
||||
0xCD2A5925D9681F90ULL, 0x8FDAB8CE70822903ULL, 0x48CB9AF28ABC72B6ULL, 0x0A3B7B1923564425ULL,
|
||||
0x70428B155B4EAF1EULL, 0x32B26AFEF2A4998DULL, 0xF5A348C2089AC238ULL, 0xB753A929A170F4ABULL,
|
||||
0x3971ED50550C43C1ULL, 0x7B810CBBFCE67552ULL, 0xBC902E8706D82EE7ULL, 0xFE60CF6CAF321874ULL,
|
||||
0xE224479F47CB76A0ULL, 0xA0D4A674EE214033ULL, 0x67C58448141F1B86ULL, 0x253565A3BDF52D15ULL,
|
||||
0xAB1721DA49899A7FULL, 0xE9E7C031E063ACECULL, 0x2EF6E20D1A5DF759ULL, 0x6C0603E6B3B7C1CAULL,
|
||||
0xF6FAE5C07D3274CDULL, 0xB40A042BD4D8425EULL, 0x731B26172EE619EBULL, 0x31EBC7FC870C2F78ULL,
|
||||
0xBFC9838573709812ULL, 0xFD39626EDA9AAE81ULL, 0x3A28405220A4F534ULL, 0x78D8A1B9894EC3A7ULL,
|
||||
0x649C294A61B7AD73ULL, 0x266CC8A1C85D9BE0ULL, 0xE17DEA9D3263C055ULL, 0xA38D0B769B89F6C6ULL,
|
||||
0x2DAF4F0F6FF541ACULL, 0x6F5FAEE4C61F773FULL, 0xA84E8CD83C212C8AULL, 0xEABE6D3395CB1A19ULL,
|
||||
0x90C79D3FEDD3F122ULL, 0xD2377CD44439C7B1ULL, 0x15265EE8BE079C04ULL, 0x57D6BF0317EDAA97ULL,
|
||||
0xD9F4FB7AE3911DFDULL, 0x9B041A914A7B2B6EULL, 0x5C1538ADB04570DBULL, 0x1EE5D94619AF4648ULL,
|
||||
0x02A151B5F156289CULL, 0x4051B05E58BC1E0FULL, 0x87409262A28245BAULL, 0xC5B073890B687329ULL,
|
||||
0x4B9237F0FF14C443ULL, 0x0962D61B56FEF2D0ULL, 0xCE73F427ACC0A965ULL, 0x8C8315CC052A9FF6ULL,
|
||||
0x3A80143F5CF17F13ULL, 0x7870F5D4F51B4980ULL, 0xBF61D7E80F251235ULL, 0xFD913603A6CF24A6ULL,
|
||||
0x73B3727A52B393CCULL, 0x31439391FB59A55FULL, 0xF652B1AD0167FEEAULL, 0xB4A25046A88DC879ULL,
|
||||
0xA8E6D8B54074A6ADULL, 0xEA16395EE99E903EULL, 0x2D071B6213A0CB8BULL, 0x6FF7FA89BA4AFD18ULL,
|
||||
0xE1D5BEF04E364A72ULL, 0xA3255F1BE7DC7CE1ULL, 0x64347D271DE22754ULL, 0x26C49CCCB40811C7ULL,
|
||||
0x5CBD6CC0CC10FAFCULL, 0x1E4D8D2B65FACC6FULL, 0xD95CAF179FC497DAULL, 0x9BAC4EFC362EA149ULL,
|
||||
0x158E0A85C2521623ULL, 0x577EEB6E6BB820B0ULL, 0x906FC95291867B05ULL, 0xD29F28B9386C4D96ULL,
|
||||
0xCEDBA04AD0952342ULL, 0x8C2B41A1797F15D1ULL, 0x4B3A639D83414E64ULL, 0x09CA82762AAB78F7ULL,
|
||||
0x87E8C60FDED7CF9DULL, 0xC51827E4773DF90EULL, 0x020905D88D03A2BBULL, 0x40F9E43324E99428ULL,
|
||||
0x2CFFE7D5975E55E2ULL, 0x6E0F063E3EB46371ULL, 0xA91E2402C48A38C4ULL, 0xEBEEC5E96D600E57ULL,
|
||||
0x65CC8190991CB93DULL, 0x273C607B30F68FAEULL, 0xE02D4247CAC8D41BULL, 0xA2DDA3AC6322E288ULL,
|
||||
0xBE992B5F8BDB8C5CULL, 0xFC69CAB42231BACFULL, 0x3B78E888D80FE17AULL, 0x7988096371E5D7E9ULL,
|
||||
0xF7AA4D1A85996083ULL, 0xB55AACF12C735610ULL, 0x724B8ECDD64D0DA5ULL, 0x30BB6F267FA73B36ULL,
|
||||
0x4AC29F2A07BFD00DULL, 0x08327EC1AE55E69EULL, 0xCF235CFD546BBD2BULL, 0x8DD3BD16FD818BB8ULL,
|
||||
0x03F1F96F09FD3CD2ULL, 0x41011884A0170A41ULL, 0x86103AB85A2951F4ULL, 0xC4E0DB53F3C36767ULL,
|
||||
0xD8A453A01B3A09B3ULL, 0x9A54B24BB2D03F20ULL, 0x5D45907748EE6495ULL, 0x1FB5719CE1045206ULL,
|
||||
0x919735E51578E56CULL, 0xD367D40EBC92D3FFULL, 0x1476F63246AC884AULL, 0x568617D9EF46BED9ULL,
|
||||
0xE085162AB69D5E3CULL, 0xA275F7C11F7768AFULL, 0x6564D5FDE549331AULL, 0x279434164CA30589ULL,
|
||||
0xA9B6706FB8DFB2E3ULL, 0xEB46918411358470ULL, 0x2C57B3B8EB0BDFC5ULL, 0x6EA7525342E1E956ULL,
|
||||
0x72E3DAA0AA188782ULL, 0x30133B4B03F2B111ULL, 0xF7021977F9CCEAA4ULL, 0xB5F2F89C5026DC37ULL,
|
||||
0x3BD0BCE5A45A6B5DULL, 0x79205D0E0DB05DCEULL, 0xBE317F32F78E067BULL, 0xFCC19ED95E6430E8ULL,
|
||||
0x86B86ED5267CDBD3ULL, 0xC4488F3E8F96ED40ULL, 0x0359AD0275A8B6F5ULL, 0x41A94CE9DC428066ULL,
|
||||
0xCF8B0890283E370CULL, 0x8D7BE97B81D4019FULL, 0x4A6ACB477BEA5A2AULL, 0x089A2AACD2006CB9ULL,
|
||||
0x14DEA25F3AF9026DULL, 0x562E43B4931334FEULL, 0x913F6188692D6F4BULL, 0xD3CF8063C0C759D8ULL,
|
||||
0x5DEDC41A34BBEEB2ULL, 0x1F1D25F19D51D821ULL, 0xD80C07CD676F8394ULL, 0x9AFCE626CE85B507ULL};
|
||||
|
||||
uint64_t HtmlHelper::crc64(uint8_t *data, size_t length, uint64_t crc)
|
||||
{
|
||||
|
||||
crc = crc ^ uint64_t(-1);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
uint8_t c = data[i];
|
||||
crc = crc64_table[(crc ^ (uint8_t)c) & 0xFF] ^ (crc >> 8);
|
||||
}
|
||||
return crc ^ uint64_t(-1);
|
||||
}
|
||||
|
||||
|
||||
uint64_t HtmlHelper::crc64(const std::string&value, uint64_t crc)
|
||||
{
|
||||
crc = crc ^ uint64_t(-1);
|
||||
for (auto c: value)
|
||||
{
|
||||
crc = crc64_table[(crc ^ (uint8_t)c) & 0xFF] ^ (crc >> 8);
|
||||
}
|
||||
return crc ^ uint64_t(-1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::string HtmlHelper::generateEtag(const std::filesystem::path &path)
|
||||
{
|
||||
uint64_t crc = 0;
|
||||
crc = crc64(path.string());
|
||||
auto fTime = std::filesystem::last_write_time(path);
|
||||
crc = crc64((uint8_t*)&fTime, sizeof(fTime));
|
||||
return std::to_string(crc);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
void Run();
|
||||
// Stop everything, shutting down gracefully.
|
||||
void Stop();
|
||||
void Wait();
|
||||
void WaitForClose();
|
||||
bool IsFinished();
|
||||
|
||||
// Stop, but don't wait. Suitable for use in a signal handler.
|
||||
@@ -78,7 +78,7 @@ private:
|
||||
std::atomic<bool> stopping;
|
||||
|
||||
std::vector<CallbackEntry> postedEvents;
|
||||
bool threadJoined;
|
||||
bool threadStarted;
|
||||
std::thread serviceThread;
|
||||
std::mutex postMutex;
|
||||
};
|
||||
@@ -29,12 +29,16 @@ namespace pipedal {
|
||||
class HtmlHelper {
|
||||
|
||||
public:
|
||||
|
||||
static std::string timeToHttpDate();
|
||||
static std::string timeToHttpDate(time_t time);
|
||||
static std::string timeToHttpDate(std::filesystem::file_time_type time);
|
||||
|
||||
|
||||
static std::string encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
|
||||
static std::string encode_url_segment(const std::string &segment, bool isQuerySegment = false) {
|
||||
return encode_url_segment(segment.c_str(), segment.c_str() + segment.length(), isQuerySegment);
|
||||
}
|
||||
static void encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment = false);
|
||||
static void encode_url_segment(std::ostream&os, const std::string&segment, bool isQuerySegment = false)
|
||||
{
|
||||
@@ -54,6 +58,11 @@ public:
|
||||
static std::string SafeFileName(const std::string &name);
|
||||
static std::string HtmlEncode(const std::string& text);
|
||||
|
||||
static uint64_t crc64(uint8_t *data, size_t length,uint64_t crc = 0);
|
||||
static uint64_t crc64(const std::string&value, uint64_t crc = 0);
|
||||
|
||||
static std::string generateEtag(const std::filesystem::path &path);
|
||||
|
||||
};
|
||||
|
||||
} // namespace.
|
||||
@@ -607,7 +607,7 @@ namespace pipedal
|
||||
if (!obj) {
|
||||
write_raw("null");
|
||||
} else {
|
||||
write(obj.get());
|
||||
write(obj.value());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,10 @@ bool pipedal::IsSubdirectory(const std::filesystem::path &path, const std::files
|
||||
|
||||
if ((*i) != (*iPath))
|
||||
{
|
||||
if (*i == "" && ++i == basePath.end()) // match xyz/ (trailing /)
|
||||
{
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
++iPath;
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/>
|
||||
|
||||
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.4.77</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.4.79</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
#### NEW version 1.4.77 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. New Phaser and Graphic Equalizer plugins.
|
||||
#### NEW version 1.4.79 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. New Phaser and Graphic Equalizer plugins.
|
||||
|
||||
|
||||
|
||||
@@ -56,8 +56,12 @@ https://github.com/user-attachments/assets/9a9fd0c6-78fc-4284-8b44-6a1929c00cc6
|
||||
### [Optimizing Audio Latency](https://rerdavies.github.io/pipedal/AudioLatency.html)
|
||||
### [Command-Line Configuration of PiPedal](https://rerdavies.github.io/pipedal/CommandLine.html)
|
||||
### [Changing the Web Server Port](https://rerdavies.github.io/pipedal/ChangingTheWebServerPort.html)
|
||||
|
||||
%nbsp;
|
||||
|
||||
### [Using LV2 Audio Plugins](https://rerdavies.github.io/pipedal/UsingLv2Plugins.md)
|
||||
### [Which LV2 Plugins does PiPedal support?](https://rerdavies.github.io/pipedal/WhichLv2PluginsAreSupported.html)
|
||||
### [Support for LV2 Plugins with MOD User Interfaces](https://rerdavies.github.io/pipedal/ModUiSupport.html)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
let galleryFrame = 0;
|
||||
|
||||
let calculateWidth = function () {
|
||||
width = Math.min(maxWidth, window.innerWidth * 0.8);
|
||||
width = Math.min(maxWidth, document.documentElement.clientWidth * 0.8);
|
||||
height = width * aspectY / aspectX;
|
||||
frameWidth = width + borderWidth * 2;
|
||||
frameHeight = height + borderWidth * 2;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg132"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
sodipodi:docname="red-light-on.png"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs136"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1239"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.84431136;"
|
||||
offset="0"
|
||||
id="stop1235" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1237" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1051"><stop
|
||||
style="stop-color:#580b0b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1047" /><stop
|
||||
style="stop-color:#252525;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop1049" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1051"
|
||||
id="linearGradient1053"
|
||||
x1="16"
|
||||
y1="24"
|
||||
x2="16"
|
||||
y2="8"
|
||||
gradientUnits="userSpaceOnUse" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1239"
|
||||
id="radialGradient1241"
|
||||
cx="15.964222"
|
||||
cy="13.292918"
|
||||
fx="15.964222"
|
||||
fy="13.292918"
|
||||
r="4.8461695"
|
||||
gradientTransform="matrix(1,0,0,0.74626396,0,3.3728925)"
|
||||
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
|
||||
id="namedview134"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="23.583986"
|
||||
inkscape:cx="16.536645"
|
||||
inkscape:cy="17.066666"
|
||||
inkscape:window-width="1867"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g138" /><g
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Image"
|
||||
id="g138"><image
|
||||
width="32"
|
||||
height="32"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
|
||||
AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAQSSURB
|
||||
VFiF7VZNaFxVFP7O/Xvv3Zk3P8/JhEnsKCNp0xJokFilSJCC0GUo6NKNSxeuBBcKwZWLUsRNUSJU
|
||||
ihQyuAmYjYsORRChwUKpENJYaUpqtbGxfTN5/9dFJyo0b5pUXZkPDlw495zz3XPuuecC+9jH/x30
|
||||
BPs927bLQghHSsmVUpmUMtRa94jo/vLysg/A/BcEvEKhMCqlFEqpjIgyy7IyIsps284YY5nWOhNC
|
||||
hEmS/La0tNT7twiQ4zijlmUNKaUyR6kWB04x4CUDjPad3CLOv7G0vlBvNH6QUmaWZfkLCwsP/jGB
|
||||
Wq02whirOY4jRJK8w4153QoCpuIYPE0BABnniKREYNspSfl58+DB9yuVSgBgq91ubz0xgVarVU6S
|
||||
5GnOuRBJ8okVhi9Wt7bw7NgYRlstFMplAIC/uYn1Gzfw08oK7jkOQsu6NDE1dapYLAZBEATtdjvN
|
||||
i8EHkRsaGhpRSsGE4Xs6DE82whBT09NoNJuQUgJpCqQplJR4ql5HfXgYvdVVhETP/Hz3rjt94sRi
|
||||
vV5Hp9PJ9pyBQ4cOucVisR74/ljy4MFXzc1NNjYxgaEDBwYlDb/cvInr167hZqWSJpxPrq6tXeur
|
||||
duwMludIa207jhNHQfCaFwTMNgalchlJrzdQypUKbGPgBQEn4M35+Xk2Pz+fG0fkKTzPY0KI2KTp
|
||||
y9U4hgKQ9XogkWsCAMiSBAqAF8e4C7yKv8q84z3I9aaUyoQQKYDRYpqCAfDv3EGxWh1IwL93DxIA
|
||||
S1MYY5rdbpcVCoXcUucSGB4ejrvdrgFgZP8Y3du3oQBI297RJg6CP/ckAGAMOOe83O+WPRFQSkVK
|
||||
KcCYtYTzw3aSAFkGf30dqlCAtCxwKQEAaRwjDkNE3S6EeXjXQs7BOF+r1WpcKZXbBbkERkZGovX1
|
||||
dTJEX/8u5WEvSR4qjIHxfUS+/4iN/Nt6U0pIIS42Gg3GGMt9B3IJzM7OpgDMeKs1dyvL3nouCPj2
|
||||
6R6HmAi3HCc9eOTIF1EU8Y2NjdzXcNBDhNnZWTZx9OjG91eu1Hucv3AgisD7RnnCAFxxXYSl0tyn
|
||||
5861lVI4fvx47kzI7c9teJ7HT87MvHvHcS4uuS6ICArYUUCEJdfFr8XipY/Onv1ASsnHx8cfrdVu
|
||||
M9DpdMzMzAybnJykkWbzy2+vXq2uSjkJImYDsIwBEcEXAj9aFr5z3TSqVj+bO3/+7WazmZVKpZ7W
|
||||
euBY3tV/YHFx0QJgSSnp4zNnDt+4fv2NOIpeybKsCQCMsTWtdef5Y8cufHj69IrWmrTWPhHdf5zv
|
||||
XX9ILl++LAHoNE0F55zK5TIREbmuS9voB04B3CeigWN4zwS2sbKyYnmeZ4VhqEqlEu/7MIVCIQaw
|
||||
BSAgol1/yfaxj338AXYNhqZlqq9VAAAAAElFTkSuQmCC
|
||||
"
|
||||
id="image412"
|
||||
x="0"
|
||||
y="0" /><circle
|
||||
style="fill:url(#linearGradient1053);fill-opacity:1;stroke:#000000;stroke-width:1;stop-color:#000000;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path468"
|
||||
cx="16"
|
||||
cy="16"
|
||||
r="7.9999995" /><ellipse
|
||||
style="fill:url(#radialGradient1241);fill-opacity:1;stroke:#000000;stroke-width:0;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
|
||||
id="path1231"
|
||||
cx="15.964222"
|
||||
cy="13.292918"
|
||||
rx="4.3461695"
|
||||
ry="3.1165216" /></g></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# Run local jeckyl-hosted sevever for GitHub documentation page.
|
||||
# See https://jekyllrb.com/docs/installation/ for instructions on how to isntall jeckyll locally.
|
||||
# Then
|
||||
# cd docs
|
||||
# bundle install
|
||||
#
|
||||
cd docs
|
||||
bundle exec jekyll serve --host
|
||||
@@ -16,8 +16,11 @@
|
||||
#### [Optimizing Audio Latency](AudioLatency.md)
|
||||
#### [Command-Line Configuration of PiPedal](CommandLine.md)
|
||||
#### [Changing the Web Server Port](ChangingTheWebServerPort.md)
|
||||
|
||||
|
||||
#### [Using LV2 Audio Plugins](UsingLv2Plugins.md)
|
||||
#### [Which LV2 Plugins does PiPedal support?](WhichLv2PluginsAreSupported.md)
|
||||
#### [LV2 Plugins with MOD User Interfaces](ModUiSupport.md)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,3 +31,5 @@ gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
|
||||
# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
|
||||
# do not have a Java counterpart.
|
||||
gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]
|
||||
|
||||
gem "webrick", "~> 1.9"
|
||||
|
||||
@@ -205,14 +205,14 @@ GEM
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.8.9)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.19.0)
|
||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
nokogiri (1.18.8)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
octokit (4.25.1)
|
||||
faraday (>= 1, < 3)
|
||||
@@ -249,6 +249,7 @@ GEM
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.9.1)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -262,6 +263,7 @@ DEPENDENCIES
|
||||
tzinfo (~> 1.2)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.1)
|
||||
webrick (~> 1.9)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.19
|
||||
|
||||
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.4.77](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_arm64.deb)
|
||||
- [Ubuntu 24.x (aarch64) v1.4.77](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_arm64.deb)
|
||||
- [Ubuntu 24.x (amd64) v1.4.77](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
|
||||
- [Ubuntu 24.x (aarch64) v1.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
|
||||
- [Ubuntu 24.x (amd64) v1.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_amd64.deb)
|
||||
|
||||
|
||||
Version 1.4.77 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
|
||||
Version 1.4.79 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.4.77_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.4.79_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
page_icon: img/moduiThumb.jpg
|
||||
icon_width: 120px
|
||||
---
|
||||
## Support for LV2 Plugins with MOD User Interfaces
|
||||
|
||||
{% include pageIconL.html %}
|
||||
|
||||
Some LV2 plugins (currently a minority of plugins) provide custom plugin user interfaces based on [MOD Audio's](https://mod.audio/desktop/)
|
||||
[ModUi framework](https://wiki.mod.audio/wiki/MOD_Web_GUI_Framework). When using such a plugin, you can choose whether to use the MOD Web GUI Framework user interface (the MOD UI) or the default PiPedal user interface. For the most part, the same functionality is available in both interfaces.
|
||||
|
||||
Plugins that are distributed via Linux distributions usually do not implement MOD user interfaces; but all (or almost all) of the plugins that are available from the [PatchStorage website](https://patchstorage.com/platform/lv2-plugins/) do implement MOD user interfaces. Many plugins that are available from GitHub or other sources also implement MOD user interfaces. Often you can find updated versions of a plugin that is published by a distro on PatchStorage which does provide a MOD UI.
|
||||
|
||||
Here's an example of a MOD UI for the ZAM Eq2 plugin:
|
||||
|
||||
<img src="img/zam-modui.jpg" alt="ZAM Eq2 MOD UI" style="width: 100%; max-width: 400px;box-sizing: border-box;padding-left: 24px; padding-right: 24px; "/>
|
||||
|
||||
|
||||
And the PiPedal UI for the same plugin:
|
||||
|
||||
<img src="img/zam-pp-ui.png" alt="ZAM Eq2 PiPedal UI" style="display: block; box-sizing: border-box; padding-left: 24px; padding-right: 24px; width: 100%; max-width: 400px; margin-left: auto; margin-right: auto"/>
|
||||
|
||||
Which UI you use is entirely up to you. MOD UIs tend to be a bit unfriendly for small-format devices like phones, but they do look great in a desktop browser. Sometimes the MOD UI will make better use of available screen space; sometimes the Pipedal UI will look better. It really depends on the plugin and your personal preferences. In this particular case, the MOD UI is more space efficient, but that is not always true.
|
||||
|
||||
To select which user interface to use, tap on the UI selection button in the toolbar for the plugin. If the plugin does not provide a MOD UI, the button will be disabled.
|
||||
|
||||
<img src="img/selectUi.png" alt="MOD UI selection button" style="display: block; width: 100%; padding-left: 20px; padding-right: 20px; max-width: 300px; margin-left: auto; margin-right: auto"/>
|
||||
|
||||
Support for MOD UIs in PiPedal is still experimental, so you may encounter some issues. PiPedal does not yet support all of the features of the MOD UI framework, not because it can't, but because we haven't been able to locate examples of plugins that use those features, which makes it difficult to test. If you encounter any issues, please report them on PiPedal's [GitHub Issues page](https://github.com/rerdavies/pipedal/issues), and we will do our best to provide quick fixes.
|
||||
|
||||
|
||||
|
||||
|
||||
--------
|
||||
[<< Which Plugins are Supported?](WhichLv2PluginsAreSupported.md) | [Up](Documentation.md) | [BuildingPiPedal from Source >>](BuildingPiPedalFromSource.md)
|
||||
@@ -1,4 +1,38 @@
|
||||
# Release Notes
|
||||
## Pipedal 1.4.79 Beta
|
||||
The Big Feature:
|
||||
|
||||
- Support for custom MOD User Interfaces for plugins that prove a MOD UI. See the [MOD UI documentation page](https://rerdavies.github.io/pipedal/ModUiSupport.html) for details on how to use MOD User Interfaces with PiPedal. This feature is experimental. If you experience problems with plugins that have MOD user interfaces, please report them on the [PiPedal GitHub Issues page](https://github.com/rerdavies/pipedal/issues).
|
||||
|
||||
New in this relase:
|
||||
|
||||
- PiPedal remembers which plugin you were viewing when you reload a preset.
|
||||
- Keep Screen On setting in the PiPedal Remote Client (requires updated Android client).
|
||||
- Lock Screen Orientation setting in the PiPedal Remote Client (requires updated Android client).
|
||||
- Disable the default startup preset if it has previously caused a crash of the PiPedal process.
|
||||
|
||||
If you are using the Android PiPedal remote app, you should make sure it has upgraded to the latest version. The new Remote app has been fully rolled out and is now available in the Google Play Store. It should update automatically, but you can check by opening the App Info for PiPedal Remote. If an "Update" button is displayed, click it to upgrade to the current version right away. If you have received the update already, you should notice differences in the App startup procedure (notably an animated splash screen).
|
||||
|
||||
The PiPedal Remote Android app has been updated to provide the following new features:
|
||||
|
||||
- new Keep Screen On settings.
|
||||
- new Lock Screen Orientation setting.
|
||||
- Edit controls are automatically repositioned above the Keyboard IME when it opens.
|
||||
- The Zoomed Control feature (large controls in landscape mode on phones) has been removed. Improved scrolling provides a better solution to the problem of controls being obscured by the keyboard IME.
|
||||
- Support for Android 16 Edge-to-Edge display.
|
||||
- Reduced display flickering while connecting.
|
||||
- Complete support for Night Mode (the Android UI now follows the PiPedal Theme setting).
|
||||
- Shiny new graphics on the opening screen in support of Android 16's Edge-to-Edge display feature (but you can see it in other versions of Android as well).
|
||||
|
||||
|
||||
Bug Fixes:
|
||||
|
||||
- The "Listen for Message" buttons in the MIDI Bindings dialog now ignore CC0 (Bank Select), and CC32-63 (CC LSB) MIDI message, since CC0 is used to control preset selection.
|
||||
- Fixed errors loading plugins with MIDI Event ports.
|
||||
- Changing volume control on low-resolution displays breaks web page.
|
||||
- MIDI binding for bypass controls have been restored after having been accidentally disabled in v1.4.77.
|
||||
|
||||
|
||||
## PiPedal 1.4.77 Beta
|
||||
|
||||
New TooB Player plugin; improvements to MIDI input handling; and a slew of fit-and finish
|
||||
|
||||
@@ -9,9 +9,8 @@ following conditions:
|
||||
|
||||
- Must be remotely controllable (no hard dependency on GUI-only controls), which is true of all but a tiny minority of LV2 plugins.
|
||||
|
||||
|
||||
If you install a new LV2 plugin, PiPedal will detect the change and make it available immediately. Wait a few seconds, and the newly-installed plugin should show up in the list of available plugins.
|
||||
|
||||
PiPedal does not currently allow plugins to provide custom user interfaces; it displays controls that are declared by the LV2 plugin without customization. In practice, this means that LV2 plugins with simple sets of controls work well, but those with complex sets of controls can be difficult to use. However, we would be pleased to collaborate with developers who need more than the basic set of controls. This is a feature set that Pipedal needs to address, so we would be happy to collaborate with other developers on this. Please contact rerdavies at gmail.com for further details.
|
||||
|
||||
--------
|
||||
[<< Using LV2 Audio Plugins](UsingLv2Plugins.md) | [Up](Documentation.md) | [BuildingPiPedal from Source >>](BuildingPiPedalFromSource.md)
|
||||
[<< Using LV2 Audio Plugins](UsingLv2Plugins.md) | [Up](Documentation.md) | [MOD UI Support >>](ModUiSupport.md)
|
||||
|
||||
@@ -36,6 +36,7 @@ header_pages:
|
||||
theme: minima
|
||||
plugins:
|
||||
- jekyll-feed
|
||||
- jekyll-seo-tag
|
||||
|
||||
include:
|
||||
- .well-known
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
let galleryFrame = 0;
|
||||
|
||||
let calculateWidth = function () {
|
||||
width = Math.min(maxWidth, window.innerWidth * 0.8);
|
||||
width = Math.min(maxWidth, document.documentElement.clientWidth * 0.8);
|
||||
height = width * aspectY / aspectX;
|
||||
frameWidth = width + borderWidth * 2;
|
||||
frameHeight = height + borderWidth * 2;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<div style="max-width: 30%; width: {{page.icon_width}}; background: #FFF; z-index: -1; margin-right: 18px; float: left">
|
||||
<img src="{{page.page_icon}}" style="width: 100%;background: #FFF; z-index: -1;
|
||||
float: right" />
|
||||
<div>
|
||||
<p style="font-size: 10px; text-align: right; color: #888">Generated by DALL-E 2</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.4.77 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_arm64.deb)
|
||||
- [Ubuntu 24.x (aarch64) v1.4.77 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_arm64.deb)
|
||||
- [Ubuntu 24.x (amd64) v1.4.77 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.77/pipedal_1.4.77_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.4.79 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
|
||||
- [Ubuntu 24.x (aarch64) v1.4.79 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
|
||||
- [Ubuntu 24.x (amd64) v1.4.79 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_amd64.deb)
|
||||
|
||||
|
||||
Install the package by running
|
||||
@@ -14,7 +14,7 @@ Install the package by running
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.4.77_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.4.79_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is
|
||||
expected, and can be safely ignored.
|
||||
|
||||
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 514 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -93,7 +93,7 @@ cabir:impulseFile3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -50,7 +50,7 @@ toob:frequencyResponseVector
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB CabSim";
|
||||
|
||||
@@ -53,7 +53,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
|
||||
Convolution reverb is a notoriously compute-intensive effect. If you are having performance issues, use the Max T control to constrain the length of the impulse file to
|
||||
|
||||
@@ -51,7 +51,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ inputStage:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB Input";
|
||||
|
||||
@@ -68,7 +68,7 @@ pstage:stage3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -57,7 +57,7 @@ tonestack:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
A straightforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
A 7 octave graphic equalizer, with frequencies chosen to be useful for EQ-ing guitar signals.
|
||||
""" ;
|
||||
|
||||
@@ -92,7 +92,7 @@ myprefix:output_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-looper-four-ui>;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ myprefix:output_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-looper-one-ui>;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ toobml:sagGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
||||
using neural-network-based machine learning models of real amplifiers.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Remix a stereo input signal.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ toobNam:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
A port of Steven Atkinson's Neural Amp Modeler to LV2.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ noisegate:envelope_group
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
A noise gate is an audio processing tool that controls the volume of an audio signal
|
||||
by allowing it to pass through only when it exceeds a set threshold.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
A loose emulation of an MXR® Phase 90 Phaser.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ toobPlayer:seek
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button.
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ recordPrefix:audioFile
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-mono-ui>;
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ myprefix:loop3_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-stereo-ui>;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
TooB Tuner is a chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 63 ;
|
||||
lv2:microVersion 64 ;
|
||||
rdfs:comment """
|
||||
Volume control.
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -1484,7 +1485,7 @@ namespace pipedal
|
||||
#endif
|
||||
}
|
||||
|
||||
std::jthread *audioThread = nullptr;
|
||||
std::unique_ptr<std::jthread> audioThread;
|
||||
bool audioRunning;
|
||||
|
||||
bool block = false;
|
||||
@@ -1518,32 +1519,36 @@ namespace pipedal
|
||||
} while (frames > 0);
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
protected:
|
||||
void ReadMidiData(uint32_t audioFrame)
|
||||
{
|
||||
AlsaMidiMessage message;
|
||||
|
||||
midiEventCount = 0;
|
||||
while(alsaSequencer->ReadMessage(message,0))
|
||||
while (alsaSequencer->ReadMessage(message, 0))
|
||||
{
|
||||
size_t messageSize = message.size;
|
||||
if (messageSize == 0)
|
||||
if (messageSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (midiEventMemoryIndex + messageSize >= this->midiEventMemory.size()) {
|
||||
if (midiEventMemoryIndex + messageSize >= this->midiEventMemory.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (midiEventCount >= this->midiEvents.size()) {
|
||||
midiEvents.resize(midiEventCount*2);
|
||||
if (midiEventCount >= this->midiEvents.size())
|
||||
{
|
||||
midiEvents.resize(midiEventCount * 2);
|
||||
}
|
||||
// for now, prevent META event messages from propagating.
|
||||
if (message.data[0] == 0xFF && message.size > 1) {
|
||||
// for now, prevent META event messages from propagating.
|
||||
if (message.data[0] == 0xFF && message.size > 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
MidiEvent *pEvent = midiEvents.data() + midiEventCount++;
|
||||
pEvent->time = audioFrame;
|
||||
pEvent->size = messageSize;
|
||||
pEvent->size = messageSize;
|
||||
pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex;
|
||||
|
||||
memcpy(
|
||||
@@ -1551,11 +1556,10 @@ namespace pipedal
|
||||
message.data,
|
||||
message.size);
|
||||
midiEventMemoryIndex += messageSize;
|
||||
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
private:
|
||||
long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames)
|
||||
{
|
||||
long framesRead;
|
||||
@@ -1592,6 +1596,8 @@ namespace pipedal
|
||||
throw PiPedalStateException("Unable to start ALSA capture.");
|
||||
}
|
||||
|
||||
CrashGuardLock crashGuardLock;
|
||||
|
||||
cpuUse.SetStartTime(cpuUse.Now());
|
||||
while (true)
|
||||
{
|
||||
@@ -1759,7 +1765,7 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
|
||||
audioThread = new std::jthread([this]()
|
||||
audioThread = std::make_unique<std::jthread>([this]()
|
||||
{ AudioThread(); });
|
||||
}
|
||||
|
||||
@@ -1773,8 +1779,7 @@ namespace pipedal
|
||||
terminateAudio(true);
|
||||
if (audioThread)
|
||||
{
|
||||
this->audioThread->join();
|
||||
this->audioThread = 0;
|
||||
this->audioThread = 0; // jthread joins.
|
||||
}
|
||||
Lv2Log::debug("Audio thread joined.");
|
||||
}
|
||||
@@ -1789,9 +1794,6 @@ namespace pipedal
|
||||
AlsaSequencer::ptr alsaSequencer;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
|
||||
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
|
||||
{
|
||||
this->alsaSequencer = alsaSequencer;
|
||||
@@ -1822,7 +1824,7 @@ namespace pipedal
|
||||
{
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
{
|
||||
// delete[] buffer[i];
|
||||
delete[] buffer[i];
|
||||
buffer[i] = 0;
|
||||
}
|
||||
buffer.clear();
|
||||
@@ -1887,6 +1889,51 @@ namespace pipedal
|
||||
snd_pcm_t *captureHandle = nullptr;
|
||||
snd_pcm_hw_params_t *playbackHwParams = nullptr;
|
||||
snd_pcm_hw_params_t *captureHwParams = nullptr;
|
||||
|
||||
Finally ff_playbackHandle{
|
||||
[&playbackHandle]()
|
||||
{
|
||||
if (playbackHandle)
|
||||
{
|
||||
int rc = snd_pcm_close(playbackHandle);
|
||||
if (rc < 0)
|
||||
{
|
||||
throw std::runtime_error("snd_pcm_close failed.");
|
||||
}
|
||||
playbackHandle = nullptr;
|
||||
}
|
||||
}};
|
||||
Finally ff_captureHandle{
|
||||
[&captureHandle]()
|
||||
{
|
||||
if (captureHandle)
|
||||
{
|
||||
int rc = snd_pcm_close(captureHandle);
|
||||
if (rc < 0)
|
||||
{
|
||||
throw std::runtime_error("snd_pcm_close failed.");
|
||||
}
|
||||
|
||||
captureHandle = nullptr;
|
||||
}
|
||||
}};
|
||||
Finally ff_playbackHwParams{
|
||||
[&playbackHwParams]()
|
||||
{
|
||||
if (playbackHwParams)
|
||||
{
|
||||
snd_pcm_hw_params_free(playbackHwParams);
|
||||
}
|
||||
}};
|
||||
Finally ff_captureHwParams{
|
||||
[&captureHwParams]()
|
||||
{
|
||||
if (captureHwParams)
|
||||
{
|
||||
snd_pcm_hw_params_free(captureHwParams);
|
||||
}
|
||||
}};
|
||||
|
||||
std::string alsaDeviceName = jackServerSettings.GetAlsaInputDevice();
|
||||
bool result = false;
|
||||
|
||||
@@ -2001,28 +2048,6 @@ namespace pipedal
|
||||
result = false;
|
||||
throw;
|
||||
}
|
||||
if (playbackHwParams)
|
||||
{
|
||||
snd_pcm_hw_params_free(playbackHwParams);
|
||||
playbackHwParams = nullptr;
|
||||
}
|
||||
|
||||
if (captureHwParams)
|
||||
{
|
||||
snd_pcm_hw_params_free(captureHwParams);
|
||||
captureHwParams = nullptr;
|
||||
}
|
||||
|
||||
if (playbackHandle)
|
||||
{
|
||||
snd_pcm_close(playbackHandle);
|
||||
playbackHandle = nullptr;
|
||||
}
|
||||
if (captureHandle)
|
||||
{
|
||||
snd_pcm_close(captureHandle);
|
||||
captureHandle = nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2176,4 +2201,9 @@ namespace pipedal
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FreeAlsaGlobals()
|
||||
{
|
||||
snd_config_update_free_global(); // to get a clean Valgrind report.
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -40,5 +40,7 @@ namespace pipedal {
|
||||
// test only.
|
||||
void AlsaFormatEncodeDecodeTest(AudioDriverHost*driverHost);
|
||||
void MidiDecoderTest();
|
||||
|
||||
void FreeAlsaGlobals(); // for valgrind. Free the Alsa configuration cache.
|
||||
}
|
||||
|
||||
|
||||
@@ -931,6 +931,15 @@ private:
|
||||
uint8_t cmd = (uint8_t)(event.buffer[0] & 0xF0);
|
||||
bool isNote = cmd == 0x90 && event.buffer[2] != 0; // note on with velocity > 0.
|
||||
bool isControl = cmd == 0xB0;
|
||||
if (isControl)
|
||||
{
|
||||
// ignore bank select, and CC LSB values.
|
||||
uint8_t cc1 = (uint8_t)(event.buffer[1]);
|
||||
if (cc1 == 0 || (cc1 >= 32 && cc1 < 64))
|
||||
{
|
||||
isControl = false;
|
||||
}
|
||||
}
|
||||
if (isNote || isControl)
|
||||
{
|
||||
MidiNotifyBody notifyBody (event.buffer[0],event.buffer[1], event.buffer[2]);
|
||||
@@ -1502,8 +1511,10 @@ public:
|
||||
if (property != nullptr && value != nullptr && property->type == uris.atom_URID)
|
||||
{
|
||||
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
|
||||
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
|
||||
// this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
if (this->pNotifyCallbacks)
|
||||
{
|
||||
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,10 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
CrashGuard.cpp CrashGuard.hpp
|
||||
ModTemplateGenerator.cpp ModTemplateGenerator.hpp
|
||||
WebServerMod.cpp WebServerMod.hpp
|
||||
ModGui.cpp ModGui.hpp
|
||||
PipewireInputStream.cpp PipewireInputStream.hpp
|
||||
AudioFiles.cpp AudioFiles.hpp
|
||||
AudioFileMetadataReader.cpp AudioFileMetadataReader.hpp
|
||||
@@ -414,6 +418,8 @@ add_executable(AuxInTest
|
||||
|
||||
add_executable(pipedaltest
|
||||
testMain.cpp
|
||||
|
||||
ModGuiTest.cpp
|
||||
PipewireInputStreamTest.cpp
|
||||
|
||||
AudioFilesTest.cpp
|
||||
@@ -762,6 +768,7 @@ add_executable(pipedal_latency_test
|
||||
DummyAudioDriver.cpp DummyAudioDriver.hpp
|
||||
JackConfiguration.hpp JackConfiguration.cpp
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
CrashGuard.cpp CrashGuard.hpp
|
||||
CpuUse.hpp
|
||||
CpuUse.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "CrashGuard.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include <fstream>
|
||||
#include <Lv2Log.hpp>
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int CrashGuard::crashCount = 0;
|
||||
std::mutex CrashGuard::mutex;
|
||||
std::filesystem::path CrashGuard::fileName;
|
||||
int64_t CrashGuard::depth = 0;
|
||||
|
||||
void CrashGuard::SetCrashGuardFileName(const std::filesystem::path &path)
|
||||
{
|
||||
CrashGuard::fileName = path;
|
||||
|
||||
{
|
||||
std::ifstream f{path};
|
||||
if (f.is_open())
|
||||
{
|
||||
f >> crashCount;
|
||||
}
|
||||
if (crashCount > 0) {
|
||||
Lv2Log::info("CrashGuard: Detected previous crash, count = %d", (int)crashCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool CrashGuard::HasCrashed()
|
||||
{
|
||||
return crashCount > 4;
|
||||
}
|
||||
void CrashGuard::ClearCrashFlag()
|
||||
{
|
||||
crashCount = 0;
|
||||
try
|
||||
{
|
||||
if (fs::exists(fileName))
|
||||
{
|
||||
fs::remove(fileName);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void CrashGuard::EnterCrashGuardZone()
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
if (depth++ == 0)
|
||||
{
|
||||
if (!fileName.empty()) {
|
||||
ofstream_synced f{fileName};
|
||||
f << (crashCount + 1) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
void CrashGuard::LeaveCrashGuardZone()
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
if (--depth == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fileName.empty() && fs::exists(fileName))
|
||||
{
|
||||
fs::remove(fileName);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Crash protection.
|
||||
*
|
||||
* Detect the situation where PiPedal has crashed multiple times due to SIG_SEGVS caused by a plugin.
|
||||
*
|
||||
* The basic premise: mark segments of code as "crash protected". If Pipedal shuts down without exiting the
|
||||
* crash guard zone for some fixed number of occasions (3?), then set a flag that will prevent
|
||||
* loading of the default plugin when PiPedal next loads. Since PiPedal restarts indefinitely while
|
||||
* running as a service, this allows users to recover after they have committed a plugin that crashes
|
||||
* into their current preset.
|
||||
*
|
||||
*/
|
||||
|
||||
class CrashGuard
|
||||
{
|
||||
public:
|
||||
static void SetCrashGuardFileName(const std::filesystem::path &path);
|
||||
static bool HasCrashed();
|
||||
static void ClearCrashFlag();
|
||||
|
||||
static void EnterCrashGuardZone();
|
||||
static void LeaveCrashGuardZone();
|
||||
|
||||
private:
|
||||
static int crashCount;
|
||||
static std::mutex mutex;
|
||||
static std::filesystem::path fileName;
|
||||
static int64_t depth;
|
||||
};
|
||||
|
||||
class CrashGuardLock {
|
||||
public:
|
||||
CrashGuardLock() {
|
||||
CrashGuard::EnterCrashGuardZone();
|
||||
}
|
||||
~CrashGuardLock() {
|
||||
CrashGuard::LeaveCrashGuardZone();
|
||||
}
|
||||
CrashGuardLock(const CrashGuardLock &) = delete;
|
||||
CrashGuardLock &operator=(const CrashGuardLock &) = delete;
|
||||
CrashGuardLock(CrashGuardLock &&) = delete;
|
||||
CrashGuardLock &operator=(CrashGuardLock &&) = delete;
|
||||
};
|
||||
};
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <stdexcept>
|
||||
#include "ss.hpp"
|
||||
#include "SchedulerPriority.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -287,6 +288,7 @@ namespace pipedal
|
||||
|
||||
bool ok = true;
|
||||
|
||||
CrashGuardLock crashGuardLock;
|
||||
while (true)
|
||||
{
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ Lv2Effect::Lv2Effect(
|
||||
lilv_node_free(uriNode);
|
||||
{
|
||||
AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin);
|
||||
char *bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
||||
char* bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
||||
|
||||
std::string storagePath = pHost_->GetPluginStoragePath();
|
||||
|
||||
@@ -108,7 +108,7 @@ Lv2Effect::Lv2Effect(
|
||||
pHost_->GetMapFeature().GetMap(),
|
||||
logFeature.GetLog(),
|
||||
bundleUriString,
|
||||
storagePath);
|
||||
storagePath);
|
||||
|
||||
mapPathFeature.Prepare(&(pHost_->GetMapFeature()));
|
||||
mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath());
|
||||
@@ -117,11 +117,12 @@ Lv2Effect::Lv2Effect(
|
||||
const auto &fileProperties = info_->piPedalUI()->fileProperties();
|
||||
for (const auto &fileProperty : fileProperties)
|
||||
{
|
||||
if (!fileProperty->resourceDirectory().empty())
|
||||
{
|
||||
mapPathFeature.AddResourceFileMapping({makeAbsolutePath(fileProperty->resourceDirectory(), bundleUriString),
|
||||
makeAbsolutePath(fileProperty->directory(), pHost_->GetPluginStoragePath())});
|
||||
}
|
||||
fs::path targetPath = fileProperty->directory() / std::filesystem::path(bundleUriString).parent_path().filename();
|
||||
mapPathFeature.AddResourceFileMapping({
|
||||
bundleUriString,
|
||||
storagePath / targetPath,
|
||||
fileProperty->fileTypes()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,14 +244,41 @@ Lv2Effect::Lv2Effect(
|
||||
// now that we've loaded the preset, clear the uri, and save new state
|
||||
// Why? because lilv doesn't provide facilities for reading state.
|
||||
pedalboardItem.lv2State(this->stateInterface->Save());
|
||||
RestoreState(pedalboardItem); // reload it with OUR map/unmap handling.
|
||||
pedalboardItem.lilvPresetUri("");
|
||||
}
|
||||
else
|
||||
{
|
||||
RestoreState(pedalboardItem);
|
||||
if (!RestoreState(pedalboardItem))
|
||||
{
|
||||
if (info->hasDefaultState())
|
||||
{
|
||||
// restore the default state.
|
||||
try
|
||||
{
|
||||
// REsTORE from LV2_STATE__state default state.
|
||||
AutoLilvNode pluginNode = lilv_new_uri(pWorld, info->uri().c_str());
|
||||
LilvState *pState = lilv_state_new_from_world(pWorld, pHost->GetMapFeature().GetMap(), pluginNode);
|
||||
if (pState)
|
||||
{
|
||||
if (this->stateInterface)
|
||||
{
|
||||
this->stateInterface->RestoreState(pState);
|
||||
}
|
||||
lilv_state_free(pState);
|
||||
}
|
||||
pedalboardItem.lv2State(this->stateInterface->Save());
|
||||
RestoreState(pedalboardItem); // do it with OUR map/unmap file handling.
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to restore default state for " << info->name() << ": " << e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void Lv2Effect::RestoreState(PedalboardItem &pedalboardItem)
|
||||
bool Lv2Effect::RestoreState(PedalboardItem &pedalboardItem)
|
||||
{
|
||||
// Restore state if present.
|
||||
if (this->stateInterface)
|
||||
@@ -260,20 +288,18 @@ void Lv2Effect::RestoreState(PedalboardItem &pedalboardItem)
|
||||
if (pedalboardItem.lv2State().isValid_)
|
||||
{
|
||||
this->stateInterface->Restore(pedalboardItem.lv2State());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// set the state to default state.
|
||||
auto savedState = this->stateInterface->Save();
|
||||
pedalboardItem.lv2State(savedState);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::string name = pedalboardItem.pluginName();
|
||||
Lv2Log::warning(SS(name << ": " << e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lv2Effect::ConnectControlPorts()
|
||||
@@ -1116,4 +1142,3 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
|
||||
{
|
||||
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace pipedal
|
||||
|
||||
FileBrowserFilesFeature fileBrowserFilesFeature;
|
||||
std::unique_ptr<StateInterface> stateInterface;
|
||||
void RestoreState(PedalboardItem&pedalboardItem);
|
||||
bool RestoreState(PedalboardItem&pedalboardItem);
|
||||
LogFeature logFeature;
|
||||
std::map<std::string,AtomBuffer> patchPropertyPrototypes;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ TEST_CASE( "PluginHost memory leak", "[lv2host_leak][Build][Dev]" ) {
|
||||
{
|
||||
PluginHost host;
|
||||
|
||||
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
|
||||
host.LoadLilv("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
|
||||
}
|
||||
MemStats finalMemory = GetMemStats();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "AudioHost.hpp"
|
||||
#include "Lv2EventBufferWriter.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -401,6 +402,8 @@ void Lv2Pedalboard::UpdateAudioPorts()
|
||||
|
||||
void Lv2Pedalboard::Activate()
|
||||
{
|
||||
CrashGuardLock crashGuardLock;
|
||||
|
||||
for (int i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
this->realtimeEffects[i]->Activate();
|
||||
|
||||
@@ -65,5 +65,8 @@ namespace pipedal {
|
||||
const LV2_URID_Map *GetMap() const { return ↦}
|
||||
LV2_URID_Map *GetMap() { return ↦}
|
||||
|
||||
const LV2_URID_Unmap *GetUnmap() const { return &unmap;}
|
||||
LV2_URID_Unmap *GetUnmap() { return &unmap;}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
#include <algorithm>
|
||||
#include "json.hpp"
|
||||
#include <sstream>
|
||||
#include "PluginHost.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
MapPathFeature::MapPathFeature()
|
||||
{
|
||||
@@ -69,6 +72,27 @@ void MapPathFeature::FnFreePath(LV2_State_Free_Path_Handle handle, char* path)
|
||||
return ((MapPathFeature *)handle)->AbsolutePath(abstract_path);
|
||||
}
|
||||
|
||||
static std::string CreateMappathLink(const fs::path&sourceFile, const fs::path &resourceDirectory, const fs::path &targetDirectory)
|
||||
{
|
||||
try {
|
||||
fs::path targetFile = targetDirectory / sourceFile.filename();
|
||||
if (fs::exists(targetFile)) {
|
||||
return targetFile;
|
||||
}
|
||||
fs::create_directories(targetDirectory);
|
||||
fs::create_symlink(sourceFile,targetFile);
|
||||
return targetFile;
|
||||
}catch (const std::exception &e) {
|
||||
Lv2Log::error(SS("Failed to convert resource file to media file. "
|
||||
<< sourceFile
|
||||
<< " -> "
|
||||
<< targetDirectory
|
||||
<< " (" << e.what() << ")"
|
||||
));
|
||||
return sourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
char *MapPathFeature::AbsolutePath(const char *abstract_path)
|
||||
{
|
||||
if (strlen(abstract_path) == 0)
|
||||
@@ -76,7 +100,27 @@ char *MapPathFeature::AbsolutePath(const char *abstract_path)
|
||||
return strdup("");
|
||||
}
|
||||
std::filesystem::path t (abstract_path);
|
||||
std::string extension = t.extension();
|
||||
if (t.is_absolute()) {
|
||||
|
||||
// Handle bundle files, mapping them to the storage path for this plugin.
|
||||
for (const auto&mapping: this->resourceFileMappings)
|
||||
{
|
||||
if (IsSubdirectory(t,mapping.resourcePath))
|
||||
{
|
||||
bool allowed = false;
|
||||
for (const auto&fileType: mapping.fileTypes) {
|
||||
if (fileType.IsValidExtension(extension)) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allowed) {
|
||||
std::string result = CreateMappathLink(t,mapping.resourcePath, mapping.storagePath);
|
||||
return strdup(result.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return strdup(abstract_path);
|
||||
}
|
||||
std::filesystem::path result = storagePath / t;
|
||||
|
||||
@@ -21,17 +21,24 @@
|
||||
#include "lv2/state/state.h"
|
||||
#include <filesystem>
|
||||
#include "MapFeature.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include "PiPedalUI.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
class Lv2PluginInfo;
|
||||
struct ResourceFileMapping {
|
||||
std::string resourcePath; // absolute path of the resource directory.
|
||||
std::string storagePath; // absolute path of where resource directory files get placed.
|
||||
std::filesystem::path resourcePath; // absolute path of the resource directory.
|
||||
std::filesystem::path storagePath; // absolute path of where resource directory files get placed.
|
||||
std::vector<UiFileType> fileTypes;
|
||||
};
|
||||
class MapPathFeature
|
||||
{
|
||||
public:
|
||||
MapPathFeature();
|
||||
|
||||
void Prepare(MapFeature* map);
|
||||
void SetPluginStoragePath(const std::string&path) { storagePath = path;}
|
||||
|
||||
@@ -46,6 +53,8 @@ namespace pipedal
|
||||
const LV2_Feature*GetMakePathFeature() { return &makePathFeature;}
|
||||
const LV2_Feature*GetFreePathFeature() { return &freePathFeature;}
|
||||
private:
|
||||
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo;
|
||||
char *AbsolutePath(const char *abstract_path);
|
||||
static char *FnAbsolutePath(LV2_State_Map_Path_Handle handle,
|
||||
const char *abstract_path);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ss.hpp"
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -30,7 +31,16 @@ static std::string empty;
|
||||
const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension) const
|
||||
{
|
||||
auto iter = extensionToMimeType.find(extension);
|
||||
if (iter == extensionToMimeType.end()) return empty;
|
||||
if (iter == extensionToMimeType.end())
|
||||
{
|
||||
std::string lowerExt = ToLower(extension);
|
||||
auto iter2 = extensionToMimeType.find(lowerExt);
|
||||
if (iter2 != extensionToMimeType.end())
|
||||
{
|
||||
return iter2->second;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ ModFileTypes::ModFileTypes(const std::string &fileTypes)
|
||||
if (wellKnownType)
|
||||
{
|
||||
rootDirectories_.push_back(type);
|
||||
modFileTypes_.push_back(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -32,12 +32,14 @@ namespace pipedal
|
||||
ModFileTypes(const std::string &fileTypes);
|
||||
const std::vector<std::string> &rootDirectories() const { return rootDirectories_; }
|
||||
std::vector<std::string> &rootDirectories() { return rootDirectories_; }
|
||||
const std::vector<std::string> &modFileTypes() const { return modFileTypes_; }
|
||||
const std::vector<std::string> &fileTypes() const { return fileTypes_; }
|
||||
|
||||
static constexpr const char *DEFAULT_FILE_TYPES = "audio,nammodel,mlmodel,sf2,sfz,midisong,midiclip,*"; // (everything)
|
||||
|
||||
private:
|
||||
std::vector<std::string> rootDirectories_;
|
||||
std::vector<std::string> modFileTypes_; // e.g. "audio", "nammodel", etc.
|
||||
std::vector<std::string> fileTypes_;
|
||||
|
||||
public:
|
||||
@@ -55,6 +57,7 @@ namespace pipedal
|
||||
const std::vector<std::string> fileTypesX; // mixture of .ext and mime types. Use fileExtensions instead.
|
||||
const std::set<std::string> fileExtensions;
|
||||
|
||||
|
||||
};
|
||||
|
||||
static const std::vector<ModDirectory> &ModDirectories();
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include "ModGui.hpp"
|
||||
#include "MapFeature.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Finally.hpp"
|
||||
#include "lv2/atom/atom.h"
|
||||
|
||||
|
||||
#define MOD_GUI_PREFIX "http://moddevices.com/ns/modgui#"
|
||||
#define MOD_GUI__gui (MOD_GUI_PREFIX "gui")
|
||||
#define MOD_GUI__modgui (MOD_GUI_PREFIX "modgui")
|
||||
#define MOD_GUI__resourcesDirectory (MOD_GUI_PREFIX "resourcesDirectory")
|
||||
#define MOD_GUI__iconTemplate (MOD_GUI_PREFIX "iconTemplate")
|
||||
#define MOD_GUI__settingsTemplate (MOD_GUI_PREFIX "settingsTemplate")
|
||||
#define MOD_GUI__javascript (MOD_GUI_PREFIX "javascript")
|
||||
#define MOD_GUI__stylesheet (MOD_GUI_PREFIX "stylesheet")
|
||||
#define MOD_GUI__screenshot (MOD_GUI_PREFIX "screenshot")
|
||||
#define MOD_GUI__thumbnail (MOD_GUI_PREFIX "thumbnail")
|
||||
#define MOD_GUI__discussionURL (MOD_GUI_PREFIX "discussionURL")
|
||||
#define MOD_GUI__documentation (MOD_GUI_PREFIX "documentation")
|
||||
#define MOD_GUI__brand (MOD_GUI_PREFIX "brand")
|
||||
#define MOD_GUI__label (MOD_GUI_PREFIX "label")
|
||||
#define MOD_GUI__model (MOD_GUI_PREFIX "model")
|
||||
#define MOD_GUI__panel (MOD_GUI_PREFIX "panel")
|
||||
#define MOD_GUI__color (MOD_GUI_PREFIX "color")
|
||||
#define MOD_GUI__knob (MOD_GUI_PREFIX "knob")
|
||||
#define MOD_GUI__port (MOD_GUI_PREFIX "port")
|
||||
#define MOD_GUI__monitoredOutputs (MOD_GUI_PREFIX "monitoredOutputs")
|
||||
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
ModGuiUris::ModGuiUris(LilvWorld *pWorld, MapFeature &mapFeature)
|
||||
{
|
||||
mod_gui__gui = lilv_new_uri(pWorld, MOD_GUI__gui);
|
||||
mod_gui__modgui = lilv_new_uri(pWorld, MOD_GUI__modgui);
|
||||
mod_gui__resourceDirectory = lilv_new_uri(pWorld, MOD_GUI__resourcesDirectory);
|
||||
mod_gui__iconTemplate = lilv_new_uri(pWorld, MOD_GUI__iconTemplate);
|
||||
mod_gui__settingsTemplate = lilv_new_uri(pWorld, MOD_GUI__settingsTemplate);
|
||||
mod_gui__javascript = lilv_new_uri(pWorld, MOD_GUI__javascript);
|
||||
mod_gui__stylesheet = lilv_new_uri(pWorld, MOD_GUI__stylesheet);
|
||||
mod_gui__screenshot = lilv_new_uri(pWorld, MOD_GUI__screenshot);
|
||||
mod_gui__thumbnail = lilv_new_uri(pWorld, MOD_GUI__thumbnail);
|
||||
mod_gui__discussionURL = lilv_new_uri(pWorld, MOD_GUI__discussionURL);
|
||||
mod_gui__documentation = lilv_new_uri(pWorld, MOD_GUI__documentation);
|
||||
mod_gui__brand = lilv_new_uri(pWorld, MOD_GUI__brand);
|
||||
mod_gui__label = lilv_new_uri(pWorld, MOD_GUI__label);
|
||||
mod_gui__model = lilv_new_uri(pWorld, MOD_GUI__model);
|
||||
mod_gui__panel = lilv_new_uri(pWorld, MOD_GUI__panel);
|
||||
mod_gui__color = lilv_new_uri(pWorld, MOD_GUI__color);
|
||||
mod_gui__knob = lilv_new_uri(pWorld, MOD_GUI__knob);
|
||||
mod_gui__port = lilv_new_uri(pWorld, MOD_GUI__port);
|
||||
mod_gui__monitoredOutputs = lilv_new_uri(pWorld, MOD_GUI__monitoredOutputs);
|
||||
}
|
||||
|
||||
static const char*NonNull(const char*string) {
|
||||
return string ? string : "";
|
||||
}
|
||||
|
||||
|
||||
ModGui::ModGui(
|
||||
PluginHost *lv2Host,
|
||||
const LilvPlugin *lilvPlugin,
|
||||
const std::string &resourceDirectory,
|
||||
const LilvNode * modGuiUrl)
|
||||
: pluginUri_(lilv_node_as_uri(lilv_plugin_get_uri(lilvPlugin))),
|
||||
resourceDirectory_(resourceDirectory)
|
||||
{
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
ModGuiUris &modGuiUrids = *lv2Host->mod_gui_uris;
|
||||
PluginHost::LilvUris &lilvUris = *lv2Host->lilvUris;
|
||||
|
||||
AutoLilvNode modguiIcon = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__iconTemplate, nullptr);
|
||||
if (modguiIcon) {
|
||||
this->iconTemplate_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiIcon), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiSettingsTemplate = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__settingsTemplate, nullptr);
|
||||
if (modguiSettingsTemplate) {
|
||||
this->settingsTemplate_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiSettingsTemplate), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiJavascript = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__javascript, nullptr);
|
||||
if (modguiJavascript) {
|
||||
this->javascript_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiJavascript), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiStylesheet = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__stylesheet, nullptr);
|
||||
if (modguiStylesheet) {
|
||||
this->stylesheet_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiStylesheet), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiScreenshot = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__screenshot, nullptr);
|
||||
if (modguiScreenshot) {
|
||||
this->screenshot_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiScreenshot), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode modguiThumbnail = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__thumbnail, nullptr);
|
||||
if (modguiThumbnail) {
|
||||
this->thumbnail_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(modguiThumbnail), nullptr));
|
||||
}
|
||||
AutoLilvNode discussionUrl = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__discussionURL, nullptr);
|
||||
if (discussionUrl) {
|
||||
this->discussionUrl_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(discussionUrl), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode documentationUrl = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__documentation, nullptr);
|
||||
if (documentationUrl) {
|
||||
this->documentationUrl_ = NonNull(lilv_file_uri_parse(lilv_node_as_string(documentationUrl), nullptr));
|
||||
}
|
||||
|
||||
AutoLilvNode brand = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__brand, nullptr);
|
||||
if (brand) {
|
||||
this->brand_ = NonNull(lilv_node_as_string(brand));
|
||||
}
|
||||
|
||||
AutoLilvNode label = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__label, nullptr);
|
||||
if (label) {
|
||||
this->label_ = NonNull(lilv_node_as_string(label));
|
||||
}
|
||||
|
||||
AutoLilvNode model = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__model, nullptr);
|
||||
if (model) {
|
||||
this->model_ = NonNull(lilv_node_as_string(model));
|
||||
}
|
||||
|
||||
AutoLilvNode panel = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__panel, nullptr);
|
||||
if (panel) {
|
||||
this->panel_ = NonNull(lilv_node_as_string(panel));
|
||||
}
|
||||
|
||||
AutoLilvNode color = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__color, nullptr);
|
||||
if (color) {
|
||||
this->color_ = NonNull(lilv_node_as_string(color));
|
||||
}
|
||||
|
||||
AutoLilvNode knob = lilv_world_get(pWorld, modGuiUrl, modGuiUrids.mod_gui__knob, nullptr);
|
||||
if (knob) {
|
||||
this->knob_ = NonNull(lilv_node_as_string(knob));
|
||||
}
|
||||
|
||||
AutoLilvNodes ports = lilv_world_find_nodes(pWorld, modGuiUrl, modGuiUrids.mod_gui__port, nullptr);
|
||||
if (ports) {
|
||||
LILV_FOREACH(nodes, it, ports.Get()) {
|
||||
AutoLilvNode port_node = lilv_nodes_get(ports, it);
|
||||
this->ports_.push_back(ModGuiPort(lv2Host,port_node));
|
||||
}
|
||||
}
|
||||
std::sort(this->ports_.begin(), this->ports_.end(),
|
||||
[](const ModGuiPort &p1, const ModGuiPort &p2) {
|
||||
return p1.index() < p2.index();
|
||||
});
|
||||
AutoLilvNodes monitoredOutputs = lilv_world_find_nodes(pWorld, modGuiUrl, modGuiUrids.mod_gui__monitoredOutputs, nullptr);
|
||||
if (monitoredOutputs) {
|
||||
LILV_FOREACH(nodes, it, monitoredOutputs.Get()) {
|
||||
AutoLilvNode monitoredOutput = lilv_nodes_get(monitoredOutputs.Get(), it);
|
||||
AutoLilvNode symbol = lilv_world_get(pWorld, monitoredOutput, lilvUris.lv2core__symbol, nullptr);
|
||||
if (symbol) {
|
||||
this->monitoredOutputs_.push_back(NonNull(lilv_node_as_string(symbol)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ModGui::ptr ModGui::Create(PluginHost *lv2Host, const LilvPlugin *lilvPlugin)
|
||||
{
|
||||
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
ModGuiUris &modGuiUrids = *(lv2Host->mod_gui_uris);
|
||||
|
||||
AutoLilvNode modGuiUri;
|
||||
std::string resourceDirectory;
|
||||
|
||||
AutoLilvNodes modGuiNodes = lilv_plugin_get_value(lilvPlugin,modGuiUrids.mod_gui__gui);
|
||||
|
||||
if (!modGuiNodes) {
|
||||
return nullptr; // no mod gui found.
|
||||
}
|
||||
LILV_FOREACH(nodes, it, modGuiNodes.Get()) {
|
||||
AutoLilvNode node = lilv_nodes_get(modGuiNodes.Get(), it);
|
||||
|
||||
AutoLilvNode resourceDir = lilv_world_get(pWorld, node, modGuiUrids.mod_gui__resourceDirectory,nullptr);
|
||||
if (resourceDir) {
|
||||
resourceDirectory = lilv_file_uri_parse(lilv_node_as_string(resourceDir),nullptr);
|
||||
modGuiUri = lilv_node_duplicate(node);
|
||||
} else {
|
||||
resourceDirectory.clear();
|
||||
modGuiUri.Free();
|
||||
}
|
||||
}
|
||||
if (!modGuiUri) {
|
||||
return nullptr; // no mod gui found.
|
||||
}
|
||||
|
||||
|
||||
return std::shared_ptr<ModGui>(new ModGui(lv2Host, lilvPlugin, resourceDirectory, modGuiUri.Get()));
|
||||
}
|
||||
|
||||
|
||||
ModGuiPort::ModGuiPort(PluginHost *lv2Host, const LilvNode *portNode)
|
||||
{
|
||||
LilvWorld *pWorld = lv2Host->getWorld();
|
||||
PluginHost::LilvUris &lilvUris = *lv2Host->lilvUris;
|
||||
|
||||
this->index_ = lilv_node_as_int(lilv_world_get(pWorld, portNode, lilvUris.lv2core__index, nullptr));
|
||||
this->symbol_ = NonNull(lilv_node_as_string(lilv_world_get(pWorld, portNode, lilvUris.lv2core__symbol, nullptr)));
|
||||
this->name_ = NonNull(lilv_node_as_string(lilv_world_get(pWorld, portNode, lilvUris.lv2core__name, nullptr)));
|
||||
}
|
||||
|
||||
static json_variant UnitsObj(const std::string &label, const std::string &render, const std::string &symbol)
|
||||
{
|
||||
json_variant obj = json_variant::make_object();
|
||||
obj["label"] = label;
|
||||
obj["render"] = render;
|
||||
obj["symbol"] = symbol;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
static std::map<Units,json_variant> unitsMap
|
||||
{
|
||||
{Units::none, UnitsObj("","%f","")},
|
||||
{Units::unknown, UnitsObj("","%f","")},
|
||||
|
||||
{Units::s, UnitsObj("seconds", "%f s", "s")},
|
||||
{Units::ms, UnitsObj("milliseconds", "%f ms", "ms")},
|
||||
{Units::min, UnitsObj("minutes", "%f mins", "min")},
|
||||
{Units::bar, UnitsObj("bars", "%f bars", "bars")},
|
||||
{Units::beat, UnitsObj("beats", "%f beats", "beats")},
|
||||
{Units::frame, UnitsObj("audio frames", "%f frames", "frames")},
|
||||
{Units::m, UnitsObj("metres", "%f m", "m")},
|
||||
{Units::cm, UnitsObj("centimetres", "%f cm", "cm")},
|
||||
{Units::mm, UnitsObj("millimetres", "%f mm", "mm")},
|
||||
{Units::km, UnitsObj("kilometres", "%f km", "km")},
|
||||
{Units::inch, UnitsObj("inches", "\"%f\"", "in")},
|
||||
{Units::mile, UnitsObj("miles", "%f mi", "mi")},
|
||||
{Units::db, UnitsObj("decibels", "%f dB", "dB")},
|
||||
{Units::pc, UnitsObj("percent", "%f%%", "%")},
|
||||
{Units::coef, UnitsObj("coefficient", "* %f", "*")},
|
||||
{Units::hz, UnitsObj("hertz", "%f Hz", "Hz")},
|
||||
{Units::khz, UnitsObj("kilohertz", "%f kHz", "kHz")},
|
||||
{Units::mhz, UnitsObj("megahertz", "%f MHz", "MHz")},
|
||||
{Units::bpm, UnitsObj("beats per minute", "%f BPM", "BPM")},
|
||||
{Units::oct, UnitsObj("octaves", "%f octaves", "oct")},
|
||||
{Units::cent, UnitsObj("cents", "%f ct", "ct")},
|
||||
{Units::semitone12TET, UnitsObj("semitones", "%f semi", "semi")},
|
||||
{Units::degree, UnitsObj("degrees", "%f°", "°")},
|
||||
{Units::midiNote, UnitsObj("MIDI note", "MIDI note %d", "note")},
|
||||
};
|
||||
|
||||
static json_variant UnitsToJsonVariant(Units units)
|
||||
{
|
||||
if (unitsMap.find(units) != unitsMap.end())
|
||||
{
|
||||
return unitsMap[units];
|
||||
}
|
||||
return unitsMap[Units::none];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline json_variant MakeJsonArray(const std::vector<T> &vec)
|
||||
{
|
||||
json_variant array = json_variant::make_array();
|
||||
auto &arrayRef = *array.as_array();
|
||||
for (const auto &item : vec)
|
||||
{
|
||||
json_variant itemVariant = json_variant(item);
|
||||
arrayRef.push_back(itemVariant);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static std::string AtomTypeToRangesType(const std::string& atomType)
|
||||
{
|
||||
if (atomType == LV2_ATOM__Path ||
|
||||
atomType == LV2_ATOM__String ||
|
||||
atomType == LV2_ATOM__URI ||
|
||||
atomType == LV2_ATOM__URID)
|
||||
{
|
||||
return "s";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Float)
|
||||
{
|
||||
return "f";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Double)
|
||||
{
|
||||
return "d";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Long)
|
||||
{
|
||||
return "l";
|
||||
}
|
||||
if (atomType == LV2_ATOM__Vector)
|
||||
{
|
||||
return "v";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
json_variant pipedal::MakeModGuiTemplateData(
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo)
|
||||
{
|
||||
json_variant context = json_variant::make_object();
|
||||
auto &contextObj = *(context.as_object());
|
||||
|
||||
auto modGui = pluginInfo->modGui();
|
||||
|
||||
contextObj["brand"] = modGui->brand();
|
||||
contextObj["label"] = modGui->label();
|
||||
contextObj["model"] = modGui->model();
|
||||
contextObj["panel"] = modGui->panel();
|
||||
contextObj["color"] = modGui->color();
|
||||
contextObj["knob"] = modGui->knob();
|
||||
|
||||
json_variant controls = json_variant::make_array();
|
||||
auto &controlArray = *controls.as_array();
|
||||
size_t ix = 0;
|
||||
for (const auto& modGuiPort : modGui->ports())
|
||||
{
|
||||
const Lv2PortInfo &pluginPort = pluginInfo->getPort(modGuiPort.symbol());
|
||||
if (!pluginPort.is_control_port() || !pluginPort.is_input() || pluginPort.not_on_gui())
|
||||
{
|
||||
continue; // only include control ports
|
||||
}
|
||||
json_variant control = json_variant::make_object();
|
||||
auto &portObj = *control.as_object();
|
||||
portObj["index"] = double(pluginPort.index());
|
||||
portObj["symbol"] = pluginPort.symbol();
|
||||
portObj["name"] = modGuiPort.name();
|
||||
portObj["comment"] = pluginPort.comment();
|
||||
|
||||
portObj["units"] = UnitsToJsonVariant(pluginPort.units());
|
||||
|
||||
{
|
||||
json_variant ranges = json_variant::make_object();
|
||||
ranges["minimum"] = SS(pluginPort.min_value());
|
||||
ranges["maximum"] = SS(pluginPort.max_value());
|
||||
ranges["default"] = SS(pluginPort.default_value());
|
||||
portObj["ranges"] = json_variant::make_array();
|
||||
}
|
||||
json_variant scalePoints = json_variant::make_array();
|
||||
auto &scalePointsArray = *scalePoints.as_array();
|
||||
for (const auto &scalePoint: pluginPort.scale_points())
|
||||
{
|
||||
json_variant scalePointObj = json_variant::make_object();
|
||||
scalePointObj["valid"] = true;
|
||||
scalePointObj["label"] = scalePoint.label();
|
||||
scalePointObj["value"] = SS(scalePoint.value());
|
||||
scalePointsArray.push_back(scalePointObj);
|
||||
}
|
||||
portObj["scalePoints"] = scalePoints;
|
||||
|
||||
controlArray.push_back(control);
|
||||
}
|
||||
contextObj["controls"] = controls;
|
||||
|
||||
json_variant effect = json_variant::make_object();
|
||||
contextObj["effect"] = effect;
|
||||
|
||||
json_variant ports = json_variant::make_object();
|
||||
(*effect.as_object())["ports"] = ports;
|
||||
|
||||
json_variant audio = json_variant::make_object();
|
||||
(*ports.as_object())["audio"] = audio;
|
||||
|
||||
json_variant audio_input = json_variant::make_array();
|
||||
(*audio.as_object())["input"] = audio_input;
|
||||
|
||||
json_variant audio_output = json_variant::make_array();
|
||||
(*audio.as_object())["output"] = audio_output;
|
||||
json_variant midi = json_variant::make_object();
|
||||
(*ports.as_object())["midi"] = midi;
|
||||
|
||||
json_variant midi_input = json_variant::make_array();
|
||||
(*midi.as_object())["input"] = midi_input;
|
||||
json_variant midi_output = json_variant::make_array();
|
||||
(*midi.as_object())["output"] = midi_output;
|
||||
|
||||
json_variant cv = json_variant::make_object();
|
||||
(*ports.as_object())["cv"] = cv;
|
||||
|
||||
json_variant cv_input = json_variant::make_array();
|
||||
(*cv.as_object())["input"] = cv_input;
|
||||
json_variant cv_output = json_variant::make_array();
|
||||
(*cv.as_object())["output"] = cv_output;
|
||||
|
||||
{
|
||||
json_variant parametersObj = json_variant::make_object();
|
||||
json_variant pathObj = json_variant::make_array();
|
||||
parametersObj["path"] = pathObj;
|
||||
|
||||
for (const auto &patchProperty: pluginInfo->patchProperties())
|
||||
{
|
||||
json_variant parameterObj = json_variant::make_object();
|
||||
parameterObj["valid"] = true;
|
||||
parameterObj["readable"] = patchProperty.readable();
|
||||
parameterObj["writable"] = patchProperty.writable();
|
||||
parameterObj["uri"] = patchProperty.uri();
|
||||
parameterObj["label"] = patchProperty.label();
|
||||
parameterObj["type"] = patchProperty.type();
|
||||
|
||||
json_variant rangesObj = json_variant::make_object();
|
||||
rangesObj["type"] = AtomTypeToRangesType(patchProperty.type());
|
||||
rangesObj["atomType"] = patchProperty.type();
|
||||
parameterObj["ranges"] = rangesObj;
|
||||
|
||||
parameterObj["comment"] = patchProperty.comment();
|
||||
parameterObj["shortName"] = patchProperty.shortName();
|
||||
|
||||
parameterObj["fileTypes"] = MakeJsonArray(patchProperty.fileTypes());
|
||||
parameterObj["supportedExtensions"] = MakeJsonArray(patchProperty.supportedExtensions());
|
||||
|
||||
if (patchProperty.type() == LV2_ATOM__Path && patchProperty.writable()) {
|
||||
// Feed them no files; they will be added later.
|
||||
json_variant filesObj = json_variant::make_array();
|
||||
json_variant fileObj = json_variant::make_object();
|
||||
fileObj["filetype"] = "{{filetype}}"; // magic tags that we will use to generate files at runtime.
|
||||
fileObj["fullname"] = "{{fullname}}";
|
||||
fileObj["basename"] = "{{basename}}";
|
||||
filesObj.as_array()->push_back(fileObj);
|
||||
parameterObj["files"] = filesObj;
|
||||
|
||||
pathObj.as_array()->push_back(parameterObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
effect["parameters"] = parametersObj;
|
||||
|
||||
|
||||
}
|
||||
|
||||
for (const auto &pluginPort : pluginInfo->ports())
|
||||
{
|
||||
if (pluginPort->is_control_port())
|
||||
{
|
||||
continue; // only include control ports
|
||||
}
|
||||
if (pluginPort->is_audio_port())
|
||||
{
|
||||
json_variant audioPort = json_variant::make_object();
|
||||
auto &audioPortObj = *audioPort.as_object();
|
||||
audioPortObj["index"] = double(pluginPort->index());
|
||||
audioPortObj["symbol"] = pluginPort->symbol();
|
||||
audioPortObj["name"] = pluginPort->name();
|
||||
if (pluginPort->is_input())
|
||||
{
|
||||
audio_input.as_array()->push_back(audioPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_output.as_array()->push_back(audioPort);
|
||||
}
|
||||
}
|
||||
else if (pluginPort->is_atom_port())
|
||||
{
|
||||
if (pluginPort->supports_midi())
|
||||
{
|
||||
json_variant midiPort = json_variant::make_object();
|
||||
auto &midiPortObj = *midiPort.as_object();
|
||||
midiPortObj["index"] = double(pluginPort->index());
|
||||
midiPortObj["symbol"] = pluginPort->symbol();
|
||||
midiPortObj["name"] = pluginPort->name();
|
||||
if (pluginPort->is_input())
|
||||
{
|
||||
midi_input.as_array()->push_back(midiPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
midi_output.as_array()->push_back(midiPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* else if plugin_port->is_cv_port()... */
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(ModGuiPort)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,index)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,symbol)
|
||||
JSON_MAP_REFERENCE(ModGuiPort,name)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(ModGui)
|
||||
JSON_MAP_REFERENCE(ModGui,pluginUri)
|
||||
JSON_MAP_REFERENCE(ModGui,resourceDirectory)
|
||||
JSON_MAP_REFERENCE(ModGui,iconTemplate)
|
||||
JSON_MAP_REFERENCE(ModGui,settingsTemplate)
|
||||
JSON_MAP_REFERENCE(ModGui,screenshot)
|
||||
JSON_MAP_REFERENCE(ModGui,javascript)
|
||||
JSON_MAP_REFERENCE(ModGui,stylesheet)
|
||||
JSON_MAP_REFERENCE(ModGui,thumbnail)
|
||||
JSON_MAP_REFERENCE(ModGui,discussionUrl)
|
||||
JSON_MAP_REFERENCE(ModGui,documentationUrl)
|
||||
JSON_MAP_REFERENCE(ModGui,brand)
|
||||
JSON_MAP_REFERENCE(ModGui,label)
|
||||
JSON_MAP_REFERENCE(ModGui,model)
|
||||
JSON_MAP_REFERENCE(ModGui,panel)
|
||||
JSON_MAP_REFERENCE(ModGui,color)
|
||||
JSON_MAP_REFERENCE(ModGui,knob)
|
||||
JSON_MAP_REFERENCE(ModGui,ports)
|
||||
JSON_MAP_REFERENCE(ModGui,monitoredOutputs)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lv2/urid/urid.h"
|
||||
#include <string>
|
||||
#include <lilv/lilv.h>
|
||||
#include <memory>
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "json.hpp"
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
class MapFeature;
|
||||
class Lv2PluginInfo;
|
||||
|
||||
class ModGuiUris
|
||||
{
|
||||
public:
|
||||
ModGuiUris(LilvWorld *pWorld, MapFeature &mapFeature);
|
||||
|
||||
AutoLilvNode mod_gui__gui;
|
||||
AutoLilvNode mod_gui__modgui;
|
||||
AutoLilvNode mod_gui__resourceDirectory;
|
||||
AutoLilvNode mod_gui__iconTemplate;
|
||||
AutoLilvNode mod_gui__settingsTemplate;
|
||||
AutoLilvNode mod_gui__javascript;
|
||||
AutoLilvNode mod_gui__stylesheet;
|
||||
AutoLilvNode mod_gui__screenshot;
|
||||
AutoLilvNode mod_gui__thumbnail;
|
||||
AutoLilvNode mod_gui__discussionURL;
|
||||
AutoLilvNode mod_gui__documentation;
|
||||
AutoLilvNode mod_gui__brand;
|
||||
AutoLilvNode mod_gui__label;
|
||||
AutoLilvNode mod_gui__model;
|
||||
AutoLilvNode mod_gui__panel;
|
||||
AutoLilvNode mod_gui__color;
|
||||
AutoLilvNode mod_gui__knob;
|
||||
AutoLilvNode mod_gui__port;
|
||||
AutoLilvNode mod_gui__monitoredOutputs;
|
||||
};
|
||||
|
||||
class PluginHost;
|
||||
|
||||
class ModGuiPort {
|
||||
public:
|
||||
ModGuiPort() = default;
|
||||
ModGuiPort(PluginHost *lv2Host, const LilvNode *portNode);
|
||||
private:
|
||||
uint32_t index_ = 0;
|
||||
std::string symbol_;
|
||||
std::string name_;
|
||||
public:
|
||||
uint32_t index() const { return index_; }
|
||||
void index(uint32_t value) { index_ = value; }
|
||||
const std::string &symbol() const { return symbol_; }
|
||||
void symbol(const std::string &value) { symbol_ = value; }
|
||||
const std::string &name() const { return name_; }
|
||||
void name(const std::string &value) { name_ = value; }
|
||||
|
||||
DECLARE_JSON_MAP(ModGuiPort);
|
||||
};
|
||||
|
||||
class ModGui
|
||||
{
|
||||
private:
|
||||
ModGui(PluginHost *lv2Host, const LilvPlugin *lilvPlugin, const std::string &resourceDirectory, const LilvNode *modGuiNode);
|
||||
|
||||
public:
|
||||
using self = ModGui;
|
||||
using ptr = std::shared_ptr<self>;
|
||||
static ptr Create(PluginHost *lv2Host, const LilvPlugin *lilvPlugin);
|
||||
|
||||
ModGui() = default;
|
||||
virtual ~ModGui() = default;
|
||||
|
||||
ModGui(const ModGui &) = default;
|
||||
ModGui &operator=(const ModGui &) = default;
|
||||
ModGui(ModGui &&) = default;
|
||||
ModGui &operator=(ModGui &&) = default;
|
||||
|
||||
private:
|
||||
std::string pluginUri_;
|
||||
|
||||
std::string resourceDirectory_;
|
||||
std::string iconTemplate_;
|
||||
std::string settingsTemplate_;
|
||||
std::string javascript_;
|
||||
std::string stylesheet_;
|
||||
std::string screenshot_;
|
||||
std::string thumbnail_;
|
||||
std::string discussionUrl_;
|
||||
std::string documentationUrl_;
|
||||
std::string brand_;
|
||||
std::string label_;
|
||||
std::string model_;
|
||||
std::string panel_;
|
||||
std::string color_;
|
||||
std::string knob_;
|
||||
std::vector<ModGuiPort> ports_;
|
||||
|
||||
std::vector<std::string> monitoredOutputs_;
|
||||
|
||||
public:
|
||||
const std::string &pluginUri() const { return pluginUri_; }
|
||||
void pluginUri(const std::string &value) { pluginUri_ = value; }
|
||||
|
||||
const std::string &resourceDirectory() const { return resourceDirectory_; }
|
||||
void resourceDirectory(const std::string &value) { resourceDirectory_ = value; }
|
||||
const std::string &iconTemplate() const { return iconTemplate_; }
|
||||
void iconTemplate(const std::string &value) { iconTemplate_ = value; }
|
||||
const std::string &settingsTemplate() const { return settingsTemplate_; }
|
||||
void settingsTemplate(const std::string &value) { settingsTemplate_ = value; }
|
||||
const std::string &screenshot() const { return screenshot_; }
|
||||
const std::string &javascript() const { return javascript_; }
|
||||
void javascript(const std::string &value) { javascript_ = value; }
|
||||
const std::string &stylesheet() const { return stylesheet_; }
|
||||
void stylesheet(const std::string &value) { stylesheet_ = value; }
|
||||
void screenshot(const std::string &value) { screenshot_ = value; }
|
||||
const std::string &thumbnail() const { return thumbnail_; }
|
||||
void thumbnail(const std::string &value) { thumbnail_ = value; }
|
||||
const std::string &discussionUrl() const { return discussionUrl_; }
|
||||
void discussionUrl(const std::string &value) { discussionUrl_ = value; }
|
||||
const std::string &documentationUrl() const { return documentationUrl_; }
|
||||
void documentationUrl(const std::string &value) { documentationUrl_ = value; }
|
||||
const std::string &brand() const { return brand_; }
|
||||
void brand(const std::string &value) { brand_ = value; }
|
||||
const std::string &label() const { return label_; }
|
||||
void label(const std::string &value) { label_ = value; }
|
||||
const std::string &model() const { return model_; }
|
||||
void model(const std::string &value) { model_ = value; }
|
||||
const std::string &panel() const { return panel_; }
|
||||
void panel(const std::string &value) { panel_ = value; }
|
||||
|
||||
const std::string &color() const { return color_; }
|
||||
void color(const std::string &value) { color_ = value; }
|
||||
const std::string &knob() const { return knob_; }
|
||||
void knob(const std::string &value) { knob_ = value; }
|
||||
|
||||
const std::vector<ModGuiPort> &ports() const { return ports_; }
|
||||
std::vector<ModGuiPort> &ports() { return ports_; }
|
||||
void ports(const std::vector<ModGuiPort> &value) { ports_ = value; }
|
||||
|
||||
|
||||
const std::vector<std::string> &monitoredOutputs() const { return monitoredOutputs_; }
|
||||
std::vector<std::string> &monitoredOutputs() { return monitoredOutputs_; }
|
||||
void monitoredOutputs(const std::vector<std::string> &value) { monitoredOutputs_ = value; }
|
||||
|
||||
DECLARE_JSON_MAP(ModGui);
|
||||
|
||||
};
|
||||
|
||||
json_variant MakeModGuiTemplateData(
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo
|
||||
);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2025 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "pch.h"
|
||||
#include "catch.hpp"
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "ModTemplateGenerator.hpp"
|
||||
|
||||
#include "PiPedalModel.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
class PluginHostTest {
|
||||
|
||||
public:
|
||||
static void TestInit()
|
||||
{
|
||||
PiPedalModel model;
|
||||
model.GetPluginHost().LoadLilv();
|
||||
const auto&plugins = model.GetPluginHost().GetPlugins();
|
||||
REQUIRE(!plugins.empty());
|
||||
size_t modGuicount = 0;
|
||||
for (const auto&plugin : plugins)
|
||||
{
|
||||
REQUIRE(plugin != nullptr);
|
||||
|
||||
if (plugin->modGui())
|
||||
{
|
||||
modGuicount++;
|
||||
const auto&modGui = plugin->modGui();
|
||||
REQUIRE(!modGui->iconTemplate().empty());
|
||||
REQUIRE(modGui->javascript().empty());
|
||||
REQUIRE(!modGui->stylesheet().empty());
|
||||
REQUIRE(!modGui->screenshot().empty());
|
||||
REQUIRE(!modGui->thumbnail().empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("ModGui Init Test", "[mod_gui_init]")
|
||||
{
|
||||
PluginHostTest::TestInit();
|
||||
}
|
||||
|
||||
TEST_CASE("ModGui Templates", "[mod_gui_templates]")
|
||||
{
|
||||
|
||||
json_variant data = json_variant::make_object();
|
||||
data["name"] = "Test Plugin";
|
||||
data["uri"] = "http://example.com/test-plugin";
|
||||
data["version"] = "1.0.0";
|
||||
data["author"] = "Test Author";
|
||||
data["cn"] = "14323";
|
||||
json_variant items = json_variant::make_array();
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
json_variant item = json_variant::make_object();
|
||||
item["label"] = "Item " + std::to_string(i + 1);
|
||||
items.as_array()->push_back(item);
|
||||
|
||||
}
|
||||
data["items"] = items;
|
||||
|
||||
data["inputs"] = json_variant::make_object();
|
||||
data["inputs"]["input1"] = "Input 1";
|
||||
data["inputs"]["input2"] = "Input 2";
|
||||
|
||||
data["_cn"] = "?cn=1234";
|
||||
data["_cns"] = "_toobamp.lv2_ToobAmp__1234";
|
||||
|
||||
std::string templateString = R"(
|
||||
<div>
|
||||
<h1>{{name}}</h1>
|
||||
<p>URI: {{uri}}</p>
|
||||
<p>Version: {{version}}</p>
|
||||
<p>Author: {{author}}</p>
|
||||
<ul>
|
||||
{{#items}}
|
||||
<li id="item-{{cn}}">{{label}}</li>
|
||||
{{/items}}
|
||||
</ul>
|
||||
<h2>{{inputs.input1}}</h2>
|
||||
<h2>{{inputs.input2}}</h2>
|
||||
<h2>{{inputs.input3}}</h2>
|
||||
<img src="img/helpIcon.png{{{cn}}}" class="{{{cns}}}_help_icon" alt="Help Icon" />
|
||||
</div>
|
||||
)";
|
||||
auto result = GenerateFromTemplateString(templateString, data);
|
||||
|
||||
cout << result;
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ModTemplateGenerator.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "util.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
|
||||
static std::string trim(const std::string &str)
|
||||
{
|
||||
size_t first = str.find_first_not_of(" \t\n\r");
|
||||
if (first == std::string::npos)
|
||||
return "";
|
||||
size_t last = str.find_last_not_of(" \t\n\r");
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
static bool variableEndsWithNumber(const std::string &variableName)
|
||||
{
|
||||
size_t pos = variableName.find_last_of(".");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (size_t i = pos + 1; i < variableName.length(); ++i)
|
||||
{
|
||||
if (!std::isdigit(variableName[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check if the last character is a digit
|
||||
return true;
|
||||
}
|
||||
void splitIndexedArrayVariable(
|
||||
const std::string &variableName,
|
||||
std::string &arrayName,
|
||||
int64_t &arrayIndex)
|
||||
{
|
||||
size_t pos = variableName.find_last_of(".");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
throw std::logic_error("Variable name does not contain an index: " + variableName);
|
||||
}
|
||||
else
|
||||
{
|
||||
arrayName = variableName.substr(0, pos);
|
||||
arrayIndex = std::stoll(variableName.substr(pos + 1));
|
||||
}
|
||||
}
|
||||
|
||||
class VariableContext
|
||||
{
|
||||
public:
|
||||
VariableContext(
|
||||
const json_variant &context,
|
||||
VariableContext *parent = nullptr)
|
||||
: context(context), parent(parent)
|
||||
{
|
||||
}
|
||||
json_variant getVariable(const std::string &name)
|
||||
{
|
||||
std::vector<std::string> parts = split(trim(name), '.');
|
||||
json_variant *current = &context;
|
||||
for (const auto &part : parts)
|
||||
{
|
||||
if (current->is_object())
|
||||
{
|
||||
auto ff = current->as_object()->find(part);
|
||||
if (ff != current->as_object()->end())
|
||||
{
|
||||
current = &ff->second;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
return parent->getVariable(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return json_variant(json_null());
|
||||
}
|
||||
}
|
||||
return *current;
|
||||
}
|
||||
|
||||
private:
|
||||
json_variant context;
|
||||
VariableContext *parent;
|
||||
};
|
||||
|
||||
static std::string GenerateTemplateFromString(
|
||||
const std::string &content,
|
||||
VariableContext &context)
|
||||
{
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
size_t ix = 0;
|
||||
size_t end = content.length();
|
||||
while (ix < end)
|
||||
{
|
||||
// copy content until we find a '{{'
|
||||
char c = content[ix++];
|
||||
if (c != '{')
|
||||
{
|
||||
ss << c;
|
||||
continue;
|
||||
}
|
||||
if (ix >= end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = content[ix++];
|
||||
if (c != '{')
|
||||
{
|
||||
ss << '{';
|
||||
ss << c;
|
||||
continue;
|
||||
}
|
||||
if (ix < end && content[ix] == '{')
|
||||
{
|
||||
// this is a '{{{', so we just copy it.
|
||||
++ix; // skip the third '{'
|
||||
size_t endTagPos = content.find("}}}", ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag '{{{' in template.");
|
||||
}
|
||||
std::string variableString = content.substr(ix, endTagPos - ix);
|
||||
ix = endTagPos + 3; // Move past the closing '}}}'
|
||||
auto result = context.getVariable("_" + variableString);
|
||||
if (result.is_null())
|
||||
{
|
||||
result = ""; // Default to empty string if variable not found
|
||||
}
|
||||
if (!result.is_string())
|
||||
{
|
||||
throw std::runtime_error("Variable '" + variableString + "' is not a string.");
|
||||
}
|
||||
ss << result.as_string();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Now we have a '{{' at position ix-2.
|
||||
size_t endTagPos = content.find("}}", ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag '{{' in template.");
|
||||
}
|
||||
std::string variableString = content.substr(ix, endTagPos - ix);
|
||||
ix = endTagPos + 2; // Move past the closing '}}'
|
||||
if (!variableString.starts_with("#"))
|
||||
{
|
||||
// a straightforward variable substitution.
|
||||
json_variant value = context.getVariable(variableString);
|
||||
if (value.is_null())
|
||||
{
|
||||
value = ""; // Default to empty string if variable not found
|
||||
}
|
||||
if (!value.is_string())
|
||||
{
|
||||
throw std::runtime_error("Variable '" + variableString + "' is not a string.");
|
||||
}
|
||||
ss << value.as_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a looping construct.
|
||||
std::string variableName = variableString.substr(1);
|
||||
std::string endTag = SS("{{/" << variableName << "}}");
|
||||
size_t endTagPos = content.find(endTag, ix);
|
||||
if (endTagPos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Unmatched opening tag for '" + variableString + "' in template.");
|
||||
}
|
||||
|
||||
std::string arrayContent = content.substr(ix, endTagPos - ix);
|
||||
|
||||
if (variableEndsWithNumber(variableName))
|
||||
{
|
||||
std::string arrayName;
|
||||
int64_t arrayIndex = 0;
|
||||
splitIndexedArrayVariable(variableName, arrayName, arrayIndex);
|
||||
|
||||
json_variant array = context.getVariable(arrayName);
|
||||
auto &arrayValue = *array.as_array();
|
||||
if (arrayIndex >= 0 && arrayIndex < static_cast<int64_t>(arrayValue.size()))
|
||||
{
|
||||
json_variant contextValue = arrayValue[(size_t)arrayIndex];
|
||||
// variable frame.
|
||||
VariableContext itemContext{contextValue, &context};
|
||||
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
json_variant variable = context.getVariable(variableName);
|
||||
if (variable.is_array())
|
||||
{
|
||||
auto &arrayValue = *variable.as_array();
|
||||
|
||||
for (auto iter = arrayValue.begin(); iter != arrayValue.end(); ++iter)
|
||||
{
|
||||
// variable frame.
|
||||
VariableContext itemContext{*iter, &context};
|
||||
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
} else if (variable.is_object())
|
||||
{
|
||||
VariableContext itemContext(variable,&context);
|
||||
ss << GenerateTemplateFromString(
|
||||
arrayContent,
|
||||
itemContext);
|
||||
}
|
||||
}
|
||||
|
||||
ix = endTagPos + endTag.length(); // Move past the closing tag
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
using namespace impl;
|
||||
|
||||
std::string GenerateFromTemplateString(
|
||||
const std::string &templateString,
|
||||
const json_variant &data)
|
||||
{
|
||||
VariableContext context{data, nullptr};
|
||||
return impl::GenerateTemplateFromString(
|
||||
templateString,
|
||||
context);
|
||||
}
|
||||
|
||||
std::string GenerateFromTemplateFile(
|
||||
const std::filesystem::path &templateFilePath,
|
||||
const json_variant &data)
|
||||
{
|
||||
std::string templateContent;
|
||||
if (!fs::exists(templateFilePath))
|
||||
{
|
||||
throw std::runtime_error("Template file not found: " + templateFilePath.string());
|
||||
}
|
||||
std::ifstream file(templateFilePath);
|
||||
if (!file.is_open())
|
||||
{
|
||||
throw std::runtime_error("Failed to open template file: " + templateFilePath.string());
|
||||
}
|
||||
templateContent.assign((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
return GenerateFromTemplateString(
|
||||
templateContent,
|
||||
data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "json_variant.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
std::string GenerateFromTemplateString(
|
||||
const std::string& templateString,
|
||||
const json_variant& data
|
||||
);
|
||||
|
||||
std::string GenerateFromTemplateFile(
|
||||
const std::filesystem::path& templateFilePath,
|
||||
const json_variant& data
|
||||
);
|
||||
}
|
||||
@@ -115,6 +115,18 @@ const ControlValue* PedalboardItem::GetControlValue(const std::string&symbol) co
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Pedalboard::SetItemUseModUi(int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
PedalboardItem*item = GetItem(pedalItemId);
|
||||
if (!item) return false;
|
||||
if (item->useModUi() != enabled)
|
||||
{
|
||||
item->useModUi(enabled);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
bool Pedalboard::SetItemEnabled(int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
PedalboardItem*item = GetItem(pedalItemId);
|
||||
@@ -508,11 +520,13 @@ JSON_MAP_BEGIN(PedalboardItem)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,topChain,IsPedalboardSplitItem)
|
||||
JSON_MAP_REFERENCE_CONDITIONAL(PedalboardItem,bottomChain,&IsPedalboardSplitItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,midiBindings)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,midiChannelBinding)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,stateUpdateCount)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,lilvPresetUri)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,title)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,useModUi)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
@@ -524,6 +538,7 @@ JSON_MAP_BEGIN(Pedalboard)
|
||||
JSON_MAP_REFERENCE(Pedalboard,nextInstanceId)
|
||||
JSON_MAP_REFERENCE(Pedalboard,snapshots)
|
||||
JSON_MAP_REFERENCE(Pedalboard,selectedSnapshot)
|
||||
JSON_MAP_REFERENCE(Pedalboard,selectedPlugin)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(SnapshotValue)
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
|
||||
};
|
||||
|
||||
class PedalboardItem: public JsonMemberWritable {
|
||||
class PedalboardItem {
|
||||
public:
|
||||
using PropertyMap = std::map<std::string,atom_object>;
|
||||
int64_t instanceId_ = 0;
|
||||
@@ -99,6 +99,7 @@ public:
|
||||
std::string lilvPresetUri_;
|
||||
std::map<std::string,std::string> pathProperties_;
|
||||
std::string title_;
|
||||
bool useModUi_ = false;
|
||||
|
||||
// non persistent state.
|
||||
PropertyMap patchProperties;
|
||||
@@ -128,6 +129,7 @@ public:
|
||||
GETTER_SETTER(stateUpdateCount)
|
||||
GETTER_SETTER_REF(lv2State)
|
||||
GETTER_SETTER_REF(title)
|
||||
GETTER_SETTER(useModUi)
|
||||
|
||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||
GETTER_SETTER_REF(lilvPresetUri)
|
||||
@@ -146,17 +148,17 @@ public:
|
||||
void AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const;
|
||||
|
||||
|
||||
virtual void write_members(json_writer&writer) const {
|
||||
writer.write_member("instanceId",instanceId_);
|
||||
writer.write_member("uri",uri_);
|
||||
writer.write_member("pluginName",pluginName_);
|
||||
writer.write_member("isEnabled",isEnabled_);
|
||||
if (isSplit())
|
||||
{
|
||||
writer.write_member("topChain",topChain_);
|
||||
writer.write_member("bottomChain",bottomChain_);
|
||||
}
|
||||
}
|
||||
// virtual void write_members(json_writer&writer) const {
|
||||
// writer.write_member("instanceId",instanceId_);
|
||||
// writer.write_member("uri",uri_);
|
||||
// writer.write_member("pluginName",pluginName_);
|
||||
// writer.write_member("isEnabled",isEnabled_);
|
||||
// if (isSplit())
|
||||
// {
|
||||
// writer.write_member("topChain",topChain_);
|
||||
// writer.write_member("bottomChain",bottomChain_);
|
||||
// }
|
||||
// }
|
||||
|
||||
DECLARE_JSON_MAP(PedalboardItem);
|
||||
};
|
||||
@@ -195,6 +197,8 @@ class Pedalboard {
|
||||
std::vector<std::shared_ptr<Snapshot>> snapshots_;
|
||||
int64_t selectedSnapshot_ = -1;
|
||||
|
||||
int64_t selectedPlugin_ = -1;
|
||||
|
||||
public:
|
||||
// deep copy, breaking shared pointers.
|
||||
Pedalboard DeepCopy();
|
||||
@@ -203,6 +207,7 @@ public:
|
||||
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
||||
bool SetItemTitle(int64_t pedalItemId, const std::string &title);
|
||||
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
||||
bool SetItemUseModUi(int64_t pedalItemId, bool enabled);
|
||||
void SetCurrentSnapshotModified(bool modified);
|
||||
|
||||
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
|
||||
@@ -221,6 +226,7 @@ public:
|
||||
GETTER_SETTER(output_volume_db)
|
||||
GETTER_SETTER_VEC(snapshots)
|
||||
GETTER_SETTER(selectedSnapshot)
|
||||
GETTER_SETTER(selectedPlugin)
|
||||
|
||||
|
||||
DECLARE_JSON_MAP(Pedalboard);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "AvahiService.hpp"
|
||||
#include "DummyAudioDriver.hpp"
|
||||
#include "AudioFiles.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
@@ -242,7 +243,7 @@ void PiPedalModel::LoadLv2PluginInfo()
|
||||
}
|
||||
|
||||
pluginChangeMonitor = std::make_unique<Lv2PluginChangeMonitor>(*this);
|
||||
pluginHost.Load(configuration.GetLv2Path().c_str());
|
||||
pluginHost.LoadLilv(configuration.GetLv2Path().c_str());
|
||||
|
||||
// Copy all presets out of Lilv data to json files
|
||||
// so that we can close lilv while we're actually
|
||||
@@ -271,20 +272,33 @@ void PiPedalModel::Load()
|
||||
|
||||
this->pedalboard = storage.GetCurrentPreset(); // the current *saved* preset.
|
||||
|
||||
|
||||
|
||||
// the current edited preset, saved only across orderly shutdowns.
|
||||
CurrentPreset currentPreset;
|
||||
try
|
||||
{
|
||||
if (storage.RestoreCurrentPreset(¤tPreset))
|
||||
|
||||
CrashGuard::SetCrashGuardFileName(storage.GetDataRoot() / "crash_guard.data");
|
||||
|
||||
if (CrashGuard::HasCrashed()) {
|
||||
// ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash.
|
||||
this->pedalboard = Pedalboard::MakeDefault();
|
||||
} else {
|
||||
CurrentPreset currentPreset;
|
||||
try
|
||||
{
|
||||
this->pedalboard = currentPreset.preset_;
|
||||
this->hasPresetChanged = currentPreset.modified_;
|
||||
if (storage.RestoreCurrentPreset(¤tPreset))
|
||||
{
|
||||
this->pedalboard = currentPreset.preset_;
|
||||
this->hasPresetChanged = currentPreset.modified_;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to load current preset. " << e.what()));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to load current preset. " << e.what()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
UpdateDefaults(&this->pedalboard);
|
||||
|
||||
std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(pluginHost.asIHost())};
|
||||
@@ -617,6 +631,24 @@ void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalbo
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetPedalboardItemUseModUi(int64_t clientId, int64_t instanceId, bool enabled)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
this->pedalboard.SetItemUseModUi(instanceId, enabled);
|
||||
|
||||
// Notify clients.
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnItemUseModUiChanged(clientId, instanceId, enabled);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PiPedalModel::SetPedalboardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
@@ -2221,7 +2253,10 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
|
||||
}
|
||||
}
|
||||
}
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
if (audioHost)
|
||||
{
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyPathPatchPropertyReceived(
|
||||
@@ -2577,8 +2612,19 @@ std::shared_ptr<Lv2Pedalboard> PiPedalModel::GetLv2Pedalboard()
|
||||
}
|
||||
return lv2Pedalboard;
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSelectedPedalboardPlugin(uint64_t clientId, uint64_t pedalboardId)
|
||||
{
|
||||
// Thinking on this:
|
||||
// 1) do NOT mark the pedalboard as changed. This shouldn't set a change flag.
|
||||
// 2) do NOT broadcast the change. Whoever set it last controls what happens when the plugin is reloaded. Meh.
|
||||
// 3) Clients must be able to save a non-changed pedalboard.
|
||||
pedalboard.selectedPlugin(pedalboardId);
|
||||
}
|
||||
|
||||
bool PiPedalModel::LoadCurrentPedalboard()
|
||||
{
|
||||
CrashGuardLock crashGuardLock;
|
||||
if (previousPedalboardLoaded && pedalboard.IsStructureIdentical(previousPedalboard))
|
||||
{
|
||||
// then we can send a snapshot update instead!
|
||||
@@ -3057,3 +3103,5 @@ bool PiPedalModel::HasTone3000Auth() const
|
||||
return storage.GetTone3000Auth() != "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace pipedal
|
||||
|
||||
virtual int64_t GetClientId() = 0;
|
||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||
virtual void OnItemUseModUiChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnInputVolumeChanged(float value) = 0;
|
||||
virtual void OnOutputVolumeChanged(float value) = 0;
|
||||
@@ -331,7 +332,9 @@ namespace pipedal
|
||||
void LoadLv2PluginInfo();
|
||||
void Load();
|
||||
|
||||
const PluginHost &GetLv2Host() const { return pluginHost; }
|
||||
const PluginHost &GetPluginHost() const { return pluginHost; }
|
||||
PluginHost &GetPluginHost() { return pluginHost; }
|
||||
|
||||
Pedalboard GetCurrentPedalboardCopy()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
@@ -346,6 +349,7 @@ namespace pipedal
|
||||
void RemoveNotificationSubsription(std::shared_ptr<IPiPedalModelSubscriber> pSubscriber);
|
||||
|
||||
void SetPedalboardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetPedalboardItemUseModUi(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
|
||||
@@ -485,6 +489,8 @@ namespace pipedal
|
||||
void SetTone3000Auth(const std::string &apiKey);
|
||||
bool HasTone3000Auth() const;
|
||||
|
||||
void SetSelectedPedalboardPlugin(uint64_t clientId, uint64_t pedalboardId);
|
||||
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
@@ -428,6 +428,22 @@ JSON_MAP_REFERENCE(PedalboardItemEnabledBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardItemEnabledBody, enabled)
|
||||
JSON_MAP_END()
|
||||
|
||||
class PedalboardItemUseModGuiBody
|
||||
{
|
||||
public:
|
||||
int64_t clientId_ = -1;
|
||||
int64_t instanceId_ = -1;
|
||||
bool useModUi_ = true;
|
||||
|
||||
DECLARE_JSON_MAP(PedalboardItemUseModGuiBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(PedalboardItemUseModGuiBody)
|
||||
JSON_MAP_REFERENCE(PedalboardItemUseModGuiBody, clientId)
|
||||
JSON_MAP_REFERENCE(PedalboardItemUseModGuiBody, instanceId)
|
||||
JSON_MAP_REFERENCE(PedalboardItemUseModGuiBody, useModUi)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class UpdateCurrentPedalboardBody
|
||||
{
|
||||
public:
|
||||
@@ -456,6 +472,21 @@ JSON_MAP_REFERENCE(SetSnapshotsBody, snapshots)
|
||||
JSON_MAP_REFERENCE(SetSnapshotsBody, selectedSnapshot)
|
||||
JSON_MAP_END()
|
||||
|
||||
class SetSelectedPedalboardPluginBody
|
||||
{
|
||||
public:
|
||||
uint64_t clientId_;
|
||||
uint64_t pluginInstanceId_;
|
||||
|
||||
DECLARE_JSON_MAP(SetSelectedPedalboardPluginBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(SetSelectedPedalboardPluginBody)
|
||||
JSON_MAP_REFERENCE(SetSelectedPedalboardPluginBody, clientId)
|
||||
JSON_MAP_REFERENCE(SetSelectedPedalboardPluginBody, pluginInstanceId)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class SnapshotModifiedBody
|
||||
{
|
||||
public:
|
||||
@@ -1284,6 +1315,12 @@ public:
|
||||
int64_t result = this->model.SaveCurrentPresetAs(this->clientId, body.name_, body.saveAfterInstanceId_);
|
||||
Reply(replyTo, "saveCurrentPresetsAs", result);
|
||||
}
|
||||
else if (message == "setSelectedPedalboardPlugin")
|
||||
{
|
||||
SetSelectedPedalboardPluginBody body;
|
||||
pReader->read(&body);
|
||||
this->model.SetSelectedPedalboardPlugin(body.clientId_,body.pluginInstanceId_);
|
||||
}
|
||||
else if (message == "savePluginPresetAs")
|
||||
{
|
||||
SavePluginPresetAsBody body;
|
||||
@@ -1303,6 +1340,11 @@ public:
|
||||
pReader->read(&body);
|
||||
model.SetPedalboardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
|
||||
}
|
||||
else if (message == "setPedalboardItemUseModUi") {
|
||||
PedalboardItemUseModGuiBody body;
|
||||
pReader->read(&body);
|
||||
model.SetPedalboardItemUseModUi(body.clientId_, body.instanceId_, body.useModUi_);
|
||||
}
|
||||
else if (message == "updateCurrentPedalboard")
|
||||
{
|
||||
{
|
||||
@@ -1331,12 +1373,12 @@ public:
|
||||
}
|
||||
else if (message == "plugins")
|
||||
{
|
||||
auto ui_plugins = model.GetLv2Host().GetUiPlugins();
|
||||
auto ui_plugins = model.GetPluginHost().GetUiPlugins();
|
||||
Reply(replyTo, "plugins", ui_plugins);
|
||||
}
|
||||
else if (message == "pluginClasses")
|
||||
{
|
||||
auto classes = model.GetLv2Host().GetLv2PluginClass();
|
||||
auto classes = model.GetPluginHost().GetLv2PluginClass();
|
||||
Reply(replyTo, "pluginClasses", classes);
|
||||
}
|
||||
else if (message == "hello")
|
||||
@@ -2221,6 +2263,15 @@ private:
|
||||
body.enabled_ = enabled;
|
||||
Send("onItemEnabledChanged", body);
|
||||
}
|
||||
virtual void OnItemUseModUiChanged(int64_t clientId, int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
PedalboardItemEnabledBody body;
|
||||
body.clientId_ = clientId;
|
||||
body.instanceId_ = pedalItemId;
|
||||
body.enabled_ = enabled;
|
||||
Send("onUseItemModUiChanged", body);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::atomic<uint64_t> PiPedalSocketHandler::nextClientId = 0;
|
||||
|
||||
@@ -140,6 +140,7 @@ UiFileType::UiFileType(PluginHost *pHost, const LilvNode *node)
|
||||
|
||||
|
||||
|
||||
|
||||
UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath)
|
||||
{
|
||||
auto pWorld = pHost->getWorld();
|
||||
@@ -455,6 +456,13 @@ bool UiFileType::IsValidExtension(const std::string&extension) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
UiFileType::UiFileType(const std::string&label, const std::string &mimeType,const std::string &fileType)
|
||||
: label_(label)
|
||||
, mimeType_(mimeType)
|
||||
, fileExtension_(fileType)
|
||||
{
|
||||
}
|
||||
|
||||
UiFileType::UiFileType(const std::string&label, const std::string &fileType)
|
||||
: label_(label)
|
||||
, fileExtension_(fileType)
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace pipedal
|
||||
{
|
||||
|
||||
class PluginHost;
|
||||
class ModFileTypes;
|
||||
|
||||
class UiFileType
|
||||
{
|
||||
@@ -87,12 +88,16 @@ namespace pipedal
|
||||
UiFileType() {}
|
||||
UiFileType(PluginHost *pHost, const LilvNode *node);
|
||||
UiFileType(const std::string &label, const std::string &fileType);
|
||||
UiFileType(const std::string &label, const std::string&mimeType, const std::string &fileType);
|
||||
|
||||
static std::vector<UiFileType> GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri);
|
||||
|
||||
const std::string &label() const { return label_; }
|
||||
void label(const std::string &value) { label_ = value; }
|
||||
const std::string &fileExtension() const { return fileExtension_; }
|
||||
void fileExtension(const std::string &value) { fileExtension_ = value; }
|
||||
const std::string &mimeType() const { return mimeType_; }
|
||||
void mimeType(const std::string &value) { mimeType_ = value; }
|
||||
bool IsValidExtension(const std::string &extension) const;
|
||||
|
||||
public:
|
||||
@@ -160,6 +165,8 @@ namespace pipedal
|
||||
|
||||
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
|
||||
std::vector<UiFileType> &fileTypes() { return fileTypes_; }
|
||||
void fileTypes(const std::vector<UiFileType> &value) { fileTypes_ = value; }
|
||||
void fileTypes(std::vector<UiFileType> &&value) { fileTypes_ = std::move(value); }
|
||||
|
||||
const std::string &patchProperty() const { return patchProperty_; }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2025 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -136,8 +136,11 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
lv2core__symbol = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
||||
lv2core__name = lilv_new_uri(pWorld, LV2_CORE__name);
|
||||
lv2core__shortName = lilv_new_uri(pWorld, LV2_CORE_PREFIX "shortName"); // ?? from mod sources
|
||||
lv2core__index = lilv_new_uri(pWorld, LV2_CORE__index);
|
||||
lv2core__Parameter = lilv_new_uri(pWorld, LV2_CORE_PREFIX "Parameter");
|
||||
lv2core__minorVersion = lilv_new_uri(pWorld, LV2_CORE__minorVersion);
|
||||
lv2core__microVersion = lilv_new_uri(pWorld, LV2_CORE__microVersion);
|
||||
|
||||
pipedalUI__ui = lilv_new_uri(pWorld, PIPEDAL_UI__ui);
|
||||
pipedalUI__fileProperties = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperties);
|
||||
@@ -150,7 +153,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
pipedalUI__mimeType = lilv_new_uri(pWorld, PIPEDAL_UI__mimeType);
|
||||
pipedalUI__outputPorts = lilv_new_uri(pWorld, PIPEDAL_UI__outputPorts);
|
||||
pipedalUI__text = lilv_new_uri(pWorld, PIPEDAL_UI__text);
|
||||
pipedalUI__ledColor = lilv_new_uri(pWorld,PIPEDAL_UI__ledColor);
|
||||
pipedalUI__ledColor = lilv_new_uri(pWorld, PIPEDAL_UI__ledColor);
|
||||
|
||||
pipedalUI__frequencyPlot = lilv_new_uri(pWorld, PIPEDAL_UI__frequencyPlot);
|
||||
pipedalUI__xLeft = lilv_new_uri(pWorld, PIPEDAL_UI__xLeft);
|
||||
@@ -159,7 +162,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
pipedalUI__yBottom = lilv_new_uri(pWorld, PIPEDAL_UI__yBottom);
|
||||
pipedalUI__xLog = lilv_new_uri(pWorld, PIPEDAL_UI__xLog);
|
||||
pipedalUI__width = lilv_new_uri(pWorld, PIPEDAL_UI__width);
|
||||
pipedalUI__graphicEq = lilv_new_uri(pWorld,PIPEDAL_UI__graphicEq);
|
||||
pipedalUI__graphicEq = lilv_new_uri(pWorld, PIPEDAL_UI__graphicEq);
|
||||
|
||||
ui__portNotification = lilv_new_uri(pWorld, LV2_UI__portNotification);
|
||||
ui__plugin = lilv_new_uri(pWorld, LV2_UI__plugin);
|
||||
@@ -171,6 +174,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
lv2__port = lilv_new_uri(pWorld, LV2_CORE__port);
|
||||
|
||||
#define MOD_PREFIX "http://moddevices.com/ns/mod#"
|
||||
|
||||
mod__label = lilv_new_uri(pWorld, MOD_PREFIX "label");
|
||||
mod__brand = lilv_new_uri(pWorld, MOD_PREFIX "brand");
|
||||
mod__preferMomentaryOffByDefault = lilv_new_uri(pWorld, MOD_PREFIX "preferMomentaryOffByDefault");
|
||||
@@ -194,16 +198,39 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
patch__writable = lilv_new_uri(pWorld, LV2_PATCH__writable);
|
||||
patch__readable = lilv_new_uri(pWorld, LV2_PATCH__readable);
|
||||
pipedal_patch__readable = lilv_new_uri(pWorld, PIPEDAL_PATCH__readable);
|
||||
pipedal_patch__readable = lilv_new_uri(pWorld, PIPEDAL_PATCH__readable);
|
||||
|
||||
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
||||
|
||||
mod__fileTypes = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#fileTypes");
|
||||
mod__supportedExtensions = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#supportedExtensions");
|
||||
}
|
||||
|
||||
void PluginHost::LilvUris::Free()
|
||||
{
|
||||
}
|
||||
static int32_t nodesAsInt(const LilvNodes *nodes)
|
||||
{
|
||||
if (!nodes)
|
||||
return 0;
|
||||
|
||||
LILV_FOREACH(nodes, iNode, nodes)
|
||||
{
|
||||
const LilvNode *node = lilv_nodes_get(nodes, iNode);
|
||||
if (!node)
|
||||
return 0;
|
||||
if (lilv_node_is_int(node))
|
||||
{
|
||||
return lilv_node_as_int(node);
|
||||
}
|
||||
if (lilv_node_is_float(node))
|
||||
{
|
||||
return static_cast<int32_t>(lilv_node_as_float(node));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::string nodeAsString(const LilvNode *node)
|
||||
{
|
||||
@@ -270,7 +297,6 @@ PluginHost::PluginHost()
|
||||
fileMetadataFeature.Prepare(mapFeature);
|
||||
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
||||
|
||||
|
||||
lv2Features.push_back(nullptr);
|
||||
|
||||
this->urids = new Urids(mapFeature);
|
||||
@@ -294,8 +320,11 @@ PluginHost::~PluginHost()
|
||||
{
|
||||
delete lilvUris;
|
||||
lilvUris = nullptr;
|
||||
|
||||
delete urids;
|
||||
urids = nullptr;
|
||||
delete mod_gui_uris;
|
||||
mod_gui_uris = nullptr;
|
||||
free_world();
|
||||
}
|
||||
|
||||
@@ -397,7 +426,7 @@ void PluginHost::LoadPluginClassesFromLilv()
|
||||
}
|
||||
}
|
||||
|
||||
void PluginHost::Load(const char *lv2Path)
|
||||
void PluginHost::LoadLilv(const char *lv2Path)
|
||||
{
|
||||
|
||||
this->plugins_.clear();
|
||||
@@ -418,6 +447,9 @@ void PluginHost::Load(const char *lv2Path)
|
||||
|
||||
lilv_world_load_all(pWorld);
|
||||
}
|
||||
// Not a safe pointer,because auto-deleting after freeWorld() would be death.
|
||||
this->mod_gui_uris = new ModGuiUris(pWorld, mapFeature);
|
||||
|
||||
// LilvNode*lv2_path = lilv_new_file_uri(pWorld,NULL,lv2Path);
|
||||
// lilv_world_set_option(world,LILV_OPTION_LV2_PATH,lv)
|
||||
|
||||
@@ -439,7 +471,8 @@ void PluginHost::Load(const char *lv2Path)
|
||||
if (pluginInfo->hasCvPorts())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
} else if (pluginInfo->hasUnsupportedPatchProperties())
|
||||
}
|
||||
else if (pluginInfo->hasUnsupportedPatchProperties())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has unsupported patch parameters).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
@@ -497,20 +530,20 @@ void PluginHost::Load(const char *lv2Path)
|
||||
if (plugin->is_valid())
|
||||
{
|
||||
#if 1
|
||||
// no plugins with more than 2 inputs or outputs.
|
||||
// no zero-input or zero-output plugins (temporarily disables midi plugins)
|
||||
// no zero output devices (permanent, I think)
|
||||
if (info.audio_inputs() > 2 || info.audio_outputs() > 2) {
|
||||
// no plugins with more than 2 inputs or outputs.
|
||||
// no zero-input or zero-output plugins (temporarily disables midi plugins)
|
||||
// no zero output devices (permanent, I think)
|
||||
if (info.audio_inputs() > 2 || info.audio_outputs() > 2)
|
||||
{
|
||||
Lv2Log::debug(
|
||||
"Plugin %s (%s) skipped. %d inputs, %d outputs.", plugin->name().c_str(), plugin->uri().c_str(),
|
||||
(int)info.audio_inputs(),(int)info.audio_outputs());
|
||||
|
||||
(int)info.audio_inputs(), (int)info.audio_outputs());
|
||||
}
|
||||
else if (info.audio_inputs() == 0 && info.audio_outputs() == 0 )
|
||||
else if (info.audio_inputs() == 0 && info.audio_outputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio i/o.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
|
||||
} else if (info.audio_inputs() == 0)
|
||||
}
|
||||
else if (info.audio_inputs() == 0)
|
||||
{
|
||||
// temporarily disable this feature.
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
@@ -536,7 +569,8 @@ void PluginHost::Load(const char *lv2Path)
|
||||
#endif
|
||||
else
|
||||
{
|
||||
if (info.audio_inputs() == 0) {
|
||||
if (info.audio_inputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("************* ZERO INPUTS: %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
ui_plugins_.push_back(std::move(info));
|
||||
@@ -613,13 +647,44 @@ bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plu
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<UiFileType> ToPiPedalFileTypes(
|
||||
const std::set<std::string> &modFileTypes)
|
||||
{
|
||||
std::vector<UiFileType> result;
|
||||
for (const auto &fileType : modFileTypes)
|
||||
{
|
||||
UiFileType uiFileType;
|
||||
std::string type = fileType;
|
||||
if (type.find("/") != std::string::npos) // mime type?
|
||||
{
|
||||
UiFileType t = UiFileType(type,type,"");
|
||||
result.push_back(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a leading dot.
|
||||
if (!type.starts_with(".")) {
|
||||
type = "." + type;
|
||||
}
|
||||
auto t = UiFileType(SS(type << " file"), type);
|
||||
result.push_back(t);
|
||||
}
|
||||
}
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const UiFileType &left, const UiFileType &right)
|
||||
{
|
||||
return left.label() < right.label();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Lv2PluginInfo::FindWritablePathPropertiesResult
|
||||
Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin)
|
||||
{
|
||||
// example:
|
||||
|
||||
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
|
||||
// a lv2:Parameter;
|
||||
// a lv2:Parameter;
|
||||
// rdfs:label "Model";
|
||||
// rdfs:range atom:Path.
|
||||
// ...
|
||||
@@ -644,7 +709,6 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->rdfs__range, lv2Host->lilvUris->atom__Path))
|
||||
{
|
||||
|
||||
|
||||
AutoLilvNode label = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->rdfs__label, nullptr);
|
||||
std::string strLabel = label.AsString();
|
||||
|
||||
@@ -662,28 +726,30 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
std::make_shared<UiFileProperty>(
|
||||
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
||||
|
||||
|
||||
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->lv2core__index, nullptr);
|
||||
int32_t index = indexNode.AsInt(-1);
|
||||
fileProperty->index(index);
|
||||
|
||||
|
||||
// if there's a pipedalui_fileTypes node, use that instead.
|
||||
|
||||
|
||||
AutoLilvNode mod__fileTypes = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
||||
|
||||
// default: everything.
|
||||
std::string fileTypes = ModFileTypes::DEFAULT_FILE_TYPES;
|
||||
std::string fileTypes = ModFileTypes::DEFAULT_FILE_TYPES;
|
||||
if (mod__fileTypes)
|
||||
{
|
||||
// "nam,nammodel"
|
||||
fileTypes = mod__fileTypes.AsString();
|
||||
}
|
||||
std::string modFileExtensions;
|
||||
AutoLilvNode mod__extensionsNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->mod__supportedExtensions, nullptr);
|
||||
{
|
||||
// "wav,flac,mp3"
|
||||
modFileExtensions = mod__extensionsNode.AsString();
|
||||
}
|
||||
std::string fileExtensions = modFileExtensions;
|
||||
ModFileTypes modFileTypes(fileTypes);
|
||||
|
||||
|
||||
|
||||
// Legacy case: audio_uploads/<plugin_directory> exists.
|
||||
|
||||
std::filesystem::path bundleDirectoryName = std::filesystem::path(bundle_path()).parent_path().filename();
|
||||
@@ -693,24 +759,32 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
|
||||
if (std::filesystem::exists(legacyUploadPath)
|
||||
&& !std::filesystem::exists(legacyUploadPath / ".migrated"))
|
||||
if (std::filesystem::exists(legacyUploadPath) && !std::filesystem::exists(legacyUploadPath / ".migrated"))
|
||||
{
|
||||
fileProperty->useLegacyModDirectory(true);
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
modFileTypes.rootDirectories().push_back(bundleDirectoryName.filename()); // push the private directory!
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modFileTypes.rootDirectories().size() == 1)
|
||||
{
|
||||
std::string modName = modFileTypes.rootDirectories()[0];
|
||||
auto modDirectory = modFileTypes.GetModDirectory(modName);
|
||||
fileProperty->directory(modDirectory->pipedalPath);
|
||||
std::set<std::string> fileExtensions = modDirectory->fileExtensions;
|
||||
if (!modFileExtensions.empty()) {
|
||||
auto extensions = split(modFileExtensions, ',');
|
||||
fileExtensions = std::set<std::string>(extensions.begin(), extensions.end());
|
||||
}
|
||||
fileProperty->fileTypes(ToPiPedalFileTypes(fileExtensions));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileProperties.push_back(fileProperty);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
unsupportedPatchProperty = true;
|
||||
}
|
||||
}
|
||||
@@ -718,24 +792,28 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
}
|
||||
FindWritablePathPropertiesResult result;
|
||||
|
||||
|
||||
|
||||
if (fileProperties.size() != 0)
|
||||
{
|
||||
std::sort(fileProperties.begin(),fileProperties.end(),[](const UiFileProperty::ptr& left,const UiFileProperty::ptr&right) {
|
||||
// properies with indexes first.
|
||||
int32_t indexL = left->index();
|
||||
if (indexL < 0) indexL = std::numeric_limits<int32_t>::max();
|
||||
int32_t indexR = right->index();
|
||||
if (indexR < 0) indexR = std::numeric_limits<int32_t>::max();
|
||||
if (indexL < indexR) return true;
|
||||
if (indexL > indexR) return false;
|
||||
std::sort(
|
||||
fileProperties.begin(),
|
||||
fileProperties.end(),
|
||||
[](const UiFileProperty::ptr &left, const UiFileProperty::ptr &right)
|
||||
{
|
||||
// properies with indexes first.
|
||||
int32_t indexL = left->index();
|
||||
if (indexL < 0)
|
||||
indexL = std::numeric_limits<int32_t>::max();
|
||||
int32_t indexR = right->index();
|
||||
if (indexR < 0)
|
||||
indexR = std::numeric_limits<int32_t>::max();
|
||||
if (indexL < indexR)
|
||||
return true;
|
||||
if (indexL > indexR)
|
||||
return false;
|
||||
|
||||
// there is no natural order. TTL indexing means that the order we see them in is random.
|
||||
// We can't order them sensibly. Let's at least order them consistently.
|
||||
return left->label() < right->label();
|
||||
|
||||
});
|
||||
// there is no natural order. TTL indexing means that the order we see them in is random.
|
||||
// We can't order them sensibly. Let's at least order them consistently.
|
||||
return left->label() < right->label(); });
|
||||
result.pipedalUi = std::make_shared<PiPedalUI>(std::move(fileProperties));
|
||||
}
|
||||
result.hasUnsupportedPatchProperties = unsupportedPatchProperty;
|
||||
@@ -750,7 +828,10 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
|
||||
std::string bundleUri = bundleUriNode.AsUri();
|
||||
|
||||
std::string bundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr);
|
||||
char *lilvBundlePath = lilv_file_uri_parse(bundleUri.c_str(), nullptr);
|
||||
std::string bundlePath = lilvBundlePath;
|
||||
lilv_free(lilvBundlePath);
|
||||
|
||||
if (bundlePath.length() == 0)
|
||||
throw std::logic_error("Bundle uri is not a file uri.");
|
||||
|
||||
@@ -764,6 +845,12 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
AutoLilvNode name = (lilv_plugin_get_name(pPlugin));
|
||||
this->name_ = nodeAsString(name);
|
||||
|
||||
AutoLilvNodes minorVersion = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->lv2core__minorVersion);
|
||||
this->minorVersion_ = nodesAsInt(minorVersion);
|
||||
|
||||
AutoLilvNodes microVersion = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->lv2core__microVersion);
|
||||
this->microVersion_ = nodesAsInt(microVersion);
|
||||
|
||||
AutoLilvNode brand = lilv_world_get(pWorld, plugUri, lv2Host->lilvUris->mod__brand, nullptr);
|
||||
this->brand_ = nodeAsString(brand);
|
||||
|
||||
@@ -838,6 +925,74 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
}
|
||||
|
||||
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
||||
// Fetch patch properties.
|
||||
{
|
||||
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__writable, nullptr);
|
||||
|
||||
bool unsupportedPatchProperty = false;
|
||||
LILV_FOREACH(nodes, iNode, patchWritables)
|
||||
{
|
||||
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
|
||||
if (propertyUri)
|
||||
{
|
||||
// a lv2:Parameter?
|
||||
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->isA, lv2Host->lilvUris->lv2core__Parameter))
|
||||
{
|
||||
Lv2PatchPropertyInfo propertyInfo{lv2Host, propertyUri};
|
||||
propertyInfo.writable(true);
|
||||
patchProperties_.push_back(std::move(propertyInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
AutoLilvNodes patchReadables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__readable, nullptr);
|
||||
|
||||
bool unsupportedPatchProperty = false;
|
||||
LILV_FOREACH(nodes, iNode, patchReadables)
|
||||
{
|
||||
AutoLilvNode propertyUri = lilv_nodes_get(patchReadables, iNode);
|
||||
if (propertyUri)
|
||||
{
|
||||
std::string uri = propertyUri.AsUri();
|
||||
bool found = false;
|
||||
for (auto &property : patchProperties_)
|
||||
{
|
||||
if (property.uri() == uri)
|
||||
{
|
||||
property.readable(true);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->isA, lv2Host->lilvUris->lv2core__Parameter))
|
||||
{
|
||||
Lv2PatchPropertyInfo propertyInfo{lv2Host, propertyUri};
|
||||
propertyInfo.readable(true);
|
||||
patchProperties_.push_back(std::move(propertyInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// default state.
|
||||
{
|
||||
AutoLilvNodes stateNodes = lilv_plugin_get_value(pPlugin, lv2Host->lilvUris->state__state);
|
||||
if (stateNodes)
|
||||
{
|
||||
auto stateNode = lilv_nodes_get_first(stateNodes);
|
||||
LilvState *defaultState = lilv_state_new_from_world(pWorld, lv2Host->GetMapFeature().GetMap(), stateNode);
|
||||
if (defaultState)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) has default state.", this->name_.c_str(), this->uri_.c_str());
|
||||
this->hasDefaultState_ = true;
|
||||
|
||||
lilv_state_free(defaultState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch pipedal plugin UI
|
||||
|
||||
@@ -886,7 +1041,11 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
++nOutputs;
|
||||
}
|
||||
}
|
||||
|
||||
if (nOutputs == 0 || nInputs == 0)
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
this->modGui_ = ModGui::Create(lv2Host, pPlugin);
|
||||
this->is_valid_ = isValid;
|
||||
}
|
||||
|
||||
@@ -1012,22 +1171,21 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
||||
this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->integer_property_uri);
|
||||
this->mod_momentaryOffByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOffByDefault);
|
||||
this->mod_momentaryOnByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOnByDefault);
|
||||
this->pipedal_graphicEq_ = lilv_port_has_property(plugin, pPort, host->lilvUris->pipedalUI__graphicEq);
|
||||
|
||||
this->pipedal_graphicEq_ = lilv_port_has_property(plugin, pPort, host->lilvUris->pipedalUI__graphicEq);
|
||||
|
||||
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->enumeration_property_uri);
|
||||
|
||||
|
||||
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__toggled);
|
||||
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri);
|
||||
this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional);
|
||||
this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger);
|
||||
|
||||
AutoLilvNode port_ledColor = lilv_port_get(plugin,pPort,host->lilvUris->pipedalUI__ledColor);
|
||||
AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor);
|
||||
if (port_ledColor)
|
||||
{
|
||||
auto value = lilv_node_as_string(port_ledColor);
|
||||
if (value) {
|
||||
if (value)
|
||||
{
|
||||
this->pipedal_ledColor_ = value;
|
||||
}
|
||||
}
|
||||
@@ -1164,6 +1322,8 @@ bool PluginHost::is_a(const std::string &class_, const std::string &target_class
|
||||
Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
||||
: uri_(plugin->uri()),
|
||||
name_(plugin->name()),
|
||||
minorVersion_(plugin->minorVersion()),
|
||||
microVersion_(plugin->microVersion()),
|
||||
brand_(plugin->brand()),
|
||||
label_(plugin->label()),
|
||||
author_name_(plugin->author_name()),
|
||||
@@ -1173,7 +1333,9 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
|
||||
audio_outputs_(0),
|
||||
description_(plugin->comment()),
|
||||
has_midi_input_(false),
|
||||
has_midi_output_(false)
|
||||
has_midi_output_(false),
|
||||
modGui_(plugin->modGui()),
|
||||
patchProperties_(plugin->patchProperties())
|
||||
{
|
||||
PLUGIN_MAP_CHECK();
|
||||
auto pluginClass = pHost->GetPluginClass(plugin->plugin_class());
|
||||
@@ -1253,7 +1415,7 @@ std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri)
|
||||
return ff->second;
|
||||
}
|
||||
|
||||
Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,Lv2Pedalboard *existingPedalboard,Lv2PedalboardErrorList &errorList)
|
||||
Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard, Lv2Pedalboard *existingPedalboard, Lv2PedalboardErrorList &errorList)
|
||||
{
|
||||
ExistingEffectMap existingEffects;
|
||||
|
||||
@@ -1261,7 +1423,8 @@ Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,L
|
||||
{
|
||||
for (auto &effect : existingPedalboard->GetSharedEffectList())
|
||||
{
|
||||
if (effect->IsLv2Effect()) {
|
||||
if (effect->IsLv2Effect())
|
||||
{
|
||||
existingEffects[effect->GetInstanceId()] = effect;
|
||||
}
|
||||
}
|
||||
@@ -1269,7 +1432,7 @@ Lv2Pedalboard *PluginHost::UpdateLv2PedalboardStructure(Pedalboard &pedalboard,L
|
||||
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
|
||||
try
|
||||
{
|
||||
pPedalboard->Prepare(this, pedalboard, errorList,&existingEffects);
|
||||
pPedalboard->Prepare(this, pedalboard, errorList, &existingEffects);
|
||||
return pPedalboard;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
@@ -1495,9 +1658,9 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
|
||||
|
||||
this->uri_ = groupUri;
|
||||
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
|
||||
LilvNode *symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr);
|
||||
AutoLilvNode symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr);
|
||||
symbol_ = nodeAsString(symbolNode);
|
||||
LilvNode *nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr);
|
||||
AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr);
|
||||
name_ = nodeAsString(nameNode);
|
||||
}
|
||||
|
||||
@@ -1589,17 +1752,16 @@ static void createTargetLinks(const std::filesystem::path &sourceDirectory, cons
|
||||
}
|
||||
}
|
||||
|
||||
std::string PluginHost::MapResourcePath(const std::string&pluginUri, const std::string&filePath)
|
||||
std::string PluginHost::MapResourcePath(const std::string &pluginUri, const std::string &filePath)
|
||||
{
|
||||
auto plugin = GetPluginInfo(pluginUri);
|
||||
if (plugin) {
|
||||
|
||||
if (plugin)
|
||||
{
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
return filePath;
|
||||
|
||||
}
|
||||
|
||||
void PluginHost::CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory)
|
||||
@@ -1679,6 +1841,47 @@ std::string PluginHost::AbstractPath(const std::string &path)
|
||||
}
|
||||
return path;
|
||||
}
|
||||
Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri)
|
||||
{
|
||||
LilvWorld *pWorld = pluginHost->getWorld();
|
||||
this->uri_ = nodeAsString(propertyUri);
|
||||
|
||||
AutoLilvNode range = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->rdfs__range, nullptr);
|
||||
if (range)
|
||||
{
|
||||
this->type_ = range.AsUri();
|
||||
}
|
||||
AutoLilvNode comment = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->rdfs__Comment, nullptr);
|
||||
if (comment)
|
||||
{
|
||||
this->comment_ = comment.AsString();
|
||||
}
|
||||
AutoLilvNode shortName = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->lv2core__shortName, nullptr);
|
||||
if (shortName)
|
||||
{
|
||||
this->shortName_ = shortName.AsString();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->shortName_ = this->label_;
|
||||
}
|
||||
|
||||
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->lv2core__index, nullptr);
|
||||
this->index_ = indexNode.AsInt(-1);
|
||||
|
||||
AutoLilvNode modFileTypesNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->mod__fileTypes, nullptr);
|
||||
if (modFileTypesNode)
|
||||
{
|
||||
std::string strFileTypes = modFileTypesNode.AsString();
|
||||
this->fileTypes_ = split(strFileTypes, ',');
|
||||
}
|
||||
AutoLilvNode modSupportedExtensionsNode = lilv_world_get(pWorld, propertyUri, pluginHost->lilvUris->mod__supportedExtensions, nullptr);
|
||||
if (modSupportedExtensionsNode)
|
||||
{
|
||||
std::string strSupportedExtensions = modSupportedExtensionsNode.AsString();
|
||||
this->supportedExtensions_ = split(strSupportedExtensions, ',');
|
||||
}
|
||||
}
|
||||
|
||||
// void PiPedalHostLogError(const std::string &error)
|
||||
// {
|
||||
@@ -1757,6 +1960,8 @@ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||
json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
||||
json_map::reference("bundle_path", &Lv2PluginInfo::bundle_path_),
|
||||
json_map::reference("uri", &Lv2PluginInfo::uri_),
|
||||
json_map::reference("minorVersion", &Lv2PluginInfo::minorVersion_),
|
||||
json_map::reference("microVersion", &Lv2PluginInfo::microVersion_),
|
||||
json_map::reference("name", &Lv2PluginInfo::name_),
|
||||
json_map::reference("brand", &Lv2PluginInfo::brand_),
|
||||
json_map::reference("label", &Lv2PluginInfo::label_),
|
||||
@@ -1774,6 +1979,10 @@ json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
||||
json_map::reference("port_groups", &Lv2PluginInfo::port_groups_),
|
||||
json_map::reference("is_valid", &Lv2PluginInfo::is_valid_),
|
||||
json_map::reference("has_factory_presets", &Lv2PluginInfo::has_factory_presets_),
|
||||
json_map::reference("modGui", &Lv2PluginInfo::modGui_),
|
||||
json_map::reference("patchProperties", &Lv2PluginInfo::patchProperties_),
|
||||
json_map::reference("hasDefaultState", &Lv2PluginInfo::hasDefaultState_),
|
||||
|
||||
}};
|
||||
|
||||
json_map::storage_type<Lv2PluginClass> Lv2PluginClass::jmap{{
|
||||
@@ -1831,6 +2040,8 @@ json_map::storage_type<Lv2PluginUiInfo>
|
||||
{
|
||||
json_map::reference("uri", &Lv2PluginUiInfo::uri_),
|
||||
json_map::reference("name", &Lv2PluginUiInfo::name_),
|
||||
json_map::reference("minorVersion", &Lv2PluginUiInfo::minorVersion_),
|
||||
json_map::reference("microVersion", &Lv2PluginUiInfo::microVersion_),
|
||||
json_map::reference("brand", &Lv2PluginUiInfo::brand_),
|
||||
json_map::reference("label", &Lv2PluginUiInfo::label_),
|
||||
json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
|
||||
@@ -1848,4 +2059,16 @@ json_map::storage_type<Lv2PluginUiInfo>
|
||||
json_map::reference("fileProperties", &Lv2PluginUiInfo::fileProperties_),
|
||||
json_map::reference("frequencyPlots", &Lv2PluginUiInfo::frequencyPlots_),
|
||||
json_map::reference("uiPortNotifications", &Lv2PluginUiInfo::uiPortNotifications_),
|
||||
json_map::reference("modGui", &Lv2PluginUiInfo::modGui_),
|
||||
json_map::reference("patchProperties", &Lv2PluginUiInfo::patchProperties_),
|
||||
}};
|
||||
JSON_MAP_BEGIN(Lv2PatchPropertyInfo)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, uri)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, label)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, type)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, comment)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, shortName)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, fileTypes)
|
||||
JSON_MAP_REFERENCE(Lv2PatchPropertyInfo, supportedExtensions)
|
||||
|
||||
JSON_MAP_END()
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2025 Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <string>
|
||||
#include "IHost.hpp"
|
||||
#include <set>
|
||||
#include "ModGui.hpp"
|
||||
|
||||
//#include "lv2.h"
|
||||
#include "Units.hpp"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "MapPathFeature.hpp"
|
||||
#include "ModGui.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -325,6 +327,38 @@ namespace pipedal
|
||||
static json_map::storage_type<Lv2PortInfo> jmap;
|
||||
};
|
||||
|
||||
class Lv2PatchPropertyInfo {
|
||||
|
||||
private:
|
||||
std::string uri_;
|
||||
bool writable_ = false;
|
||||
bool readable_ = false;
|
||||
std::string label_;
|
||||
uint32_t index_ = -1;
|
||||
std::string type_;
|
||||
std::string comment_;
|
||||
std::string shortName_;
|
||||
std::vector<std::string> fileTypes_;
|
||||
std::vector<std::string> supportedExtensions_;
|
||||
public:
|
||||
Lv2PatchPropertyInfo() {}
|
||||
Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri);
|
||||
|
||||
LV2_PROPERTY_GETSET(uri);
|
||||
LV2_PROPERTY_GETSET(writable);
|
||||
LV2_PROPERTY_GETSET(readable);
|
||||
LV2_PROPERTY_GETSET(label);
|
||||
LV2_PROPERTY_GETSET_SCALAR(index);
|
||||
LV2_PROPERTY_GETSET(type);
|
||||
LV2_PROPERTY_GETSET(comment);
|
||||
LV2_PROPERTY_GETSET(shortName);
|
||||
LV2_PROPERTY_GETSET(fileTypes);
|
||||
LV2_PROPERTY_GETSET(supportedExtensions);
|
||||
|
||||
DECLARE_JSON_MAP(Lv2PatchPropertyInfo);
|
||||
|
||||
};
|
||||
|
||||
class Lv2PortGroup
|
||||
{
|
||||
private:
|
||||
@@ -364,6 +398,8 @@ namespace pipedal
|
||||
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
||||
std::string bundle_path_;
|
||||
std::string uri_;
|
||||
uint32_t minorVersion_ = 0;
|
||||
uint32_t microVersion_ = 0;
|
||||
std::string name_;
|
||||
std::string plugin_class_;
|
||||
std::string brand_;
|
||||
@@ -380,8 +416,14 @@ namespace pipedal
|
||||
std::string comment_;
|
||||
std::vector<std::shared_ptr<Lv2PortInfo>> ports_;
|
||||
std::vector<std::shared_ptr<Lv2PortGroup>> port_groups_;
|
||||
std::vector<Lv2PatchPropertyInfo> patchProperties_;
|
||||
|
||||
bool hasDefaultState_;
|
||||
|
||||
bool is_valid_ = false;
|
||||
PiPedalUI::ptr piPedalUI_;
|
||||
ModGui::ptr modGui_;
|
||||
|
||||
bool hasUnsupportedPatchProperties_ = false;
|
||||
|
||||
bool IsSupportedFeature(const std::string &feature) const;
|
||||
@@ -390,6 +432,8 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(bundle_path)
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
LV2_PROPERTY_GETSET(name)
|
||||
LV2_PROPERTY_GETSET(minorVersion)
|
||||
LV2_PROPERTY_GETSET(microVersion)
|
||||
LV2_PROPERTY_GETSET(brand)
|
||||
LV2_PROPERTY_GETSET(label)
|
||||
LV2_PROPERTY_GETSET(plugin_class)
|
||||
@@ -406,6 +450,9 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(has_factory_presets)
|
||||
LV2_PROPERTY_GETSET(piPedalUI)
|
||||
LV2_PROPERTY_GETSET(hasUnsupportedPatchProperties)
|
||||
LV2_PROPERTY_GETSET(modGui)
|
||||
LV2_PROPERTY_GETSET(patchProperties)
|
||||
LV2_PROPERTY_GETSET(hasDefaultState)
|
||||
|
||||
const Lv2PortInfo &getPort(const std::string &symbol)
|
||||
{
|
||||
@@ -618,6 +665,8 @@ namespace pipedal
|
||||
private:
|
||||
std::string uri_;
|
||||
std::string name_;
|
||||
uint32_t minorVersion_ = 0;
|
||||
uint32_t microVersion_ = 0;;
|
||||
std::string brand_;
|
||||
std::string label_;
|
||||
std::string author_name_;
|
||||
@@ -636,10 +685,15 @@ namespace pipedal
|
||||
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||
std::vector<UiFrequencyPlot::ptr> frequencyPlots_;
|
||||
std::vector<UiPortNotification::ptr> uiPortNotifications_;
|
||||
ModGui::ptr modGui_;
|
||||
std::vector<Lv2PatchPropertyInfo> patchProperties_;
|
||||
|
||||
|
||||
public:
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
LV2_PROPERTY_GETSET(name)
|
||||
LV2_PROPERTY_GETSET(minorVersion)
|
||||
LV2_PROPERTY_GETSET(microVersion)
|
||||
LV2_PROPERTY_GETSET(brand)
|
||||
LV2_PROPERTY_GETSET(label)
|
||||
LV2_PROPERTY_GETSET(author_name)
|
||||
@@ -657,6 +711,8 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(fileProperties)
|
||||
LV2_PROPERTY_GETSET(frequencyPlots)
|
||||
LV2_PROPERTY_GETSET(uiPortNotifications)
|
||||
LV2_PROPERTY_GETSET(modGui)
|
||||
LV2_PROPERTY_GETSET(patchProperties)
|
||||
|
||||
static json_map::storage_type<Lv2PluginUiInfo> jmap;
|
||||
};
|
||||
@@ -679,6 +735,8 @@ namespace pipedal
|
||||
#endif
|
||||
friend class pipedal::AutoLilvNode;
|
||||
friend class pipedal::PiPedalUI;
|
||||
friend class PluginHostTest;
|
||||
|
||||
static const char *RDFS__comment;
|
||||
static const char *RDFS__range;
|
||||
|
||||
@@ -707,6 +765,7 @@ namespace pipedal
|
||||
AutoLilvNode invada_units__unit; // typo in invada plugins.
|
||||
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
|
||||
|
||||
|
||||
AutoLilvNode atom__bufferType;
|
||||
AutoLilvNode atom__Path;
|
||||
AutoLilvNode presets__preset;
|
||||
@@ -714,8 +773,11 @@ namespace pipedal
|
||||
AutoLilvNode rdfs__label;
|
||||
AutoLilvNode lv2core__symbol;
|
||||
AutoLilvNode lv2core__name;
|
||||
AutoLilvNode lv2core__shortName;
|
||||
AutoLilvNode lv2core__index;
|
||||
AutoLilvNode lv2core__Parameter;
|
||||
AutoLilvNode lv2core__minorVersion;
|
||||
AutoLilvNode lv2core__microVersion;
|
||||
AutoLilvNode pipedalUI__ui;
|
||||
AutoLilvNode pipedalUI__fileProperties;
|
||||
AutoLilvNode pipedalUI__directory;
|
||||
@@ -766,6 +828,7 @@ namespace pipedal
|
||||
AutoLilvNode patch__readable;
|
||||
AutoLilvNode pipedal_patch__readable;
|
||||
|
||||
|
||||
AutoLilvNode mod__brand;
|
||||
AutoLilvNode mod__label;
|
||||
AutoLilvNode mod__preferMomentaryOffByDefault;
|
||||
@@ -773,6 +836,7 @@ namespace pipedal
|
||||
AutoLilvNode dc__format;
|
||||
|
||||
AutoLilvNode mod__fileTypes;
|
||||
AutoLilvNode mod__supportedExtensions;
|
||||
AutoLilvNode pipedalui__fileTypes;
|
||||
|
||||
|
||||
@@ -817,6 +881,7 @@ namespace pipedal
|
||||
friend class Lv2PluginInfo;
|
||||
friend class Lv2PortInfo;
|
||||
friend class Lv2PortGroup;
|
||||
friend class ModGui;
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> GetPluginClass(const LilvPluginClass *pClass);
|
||||
std::shared_ptr<Lv2PluginClass> MakePluginClass(const LilvPluginClass *pClass);
|
||||
@@ -924,7 +989,9 @@ namespace pipedal
|
||||
|
||||
class Urids;
|
||||
|
||||
Urids *urids;
|
||||
Urids *urids = nullptr;
|
||||
ModGuiUris* mod_gui_uris = nullptr;
|
||||
|
||||
|
||||
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings);
|
||||
|
||||
@@ -933,7 +1000,7 @@ namespace pipedal
|
||||
|
||||
std::shared_ptr<Lv2PluginClass> GetLv2PluginClass() const;
|
||||
|
||||
std::vector<std::shared_ptr<Lv2PluginInfo>> GetPlugins() const { return plugins_; }
|
||||
const std::vector<std::shared_ptr<Lv2PluginInfo>>& GetPlugins() const { return plugins_; }
|
||||
const std::vector<Lv2PluginUiInfo> &GetUiPlugins() const { return ui_plugins_; }
|
||||
|
||||
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const;
|
||||
@@ -941,7 +1008,7 @@ namespace pipedal
|
||||
static constexpr const char *DEFAULT_LV2_PATH = "/usr/lib/lv2";
|
||||
|
||||
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
|
||||
void Load(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
|
||||
void LoadLilv(const char *lv2Path = PluginHost::DEFAULT_LV2_PATH);
|
||||
|
||||
virtual LV2_URID GetLv2Urid(const char *uri)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "json.hpp"
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#include "Lv2Log.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -83,6 +84,7 @@ static void CheckState(LV2_State_Status status)
|
||||
case LV2_State_Status::LV2_STATE_SUCCESS:
|
||||
return;
|
||||
default:
|
||||
case LV2_State_Status::LV2_STATE_ERR_UNKNOWN:
|
||||
lv2Error = "Unknown error.";
|
||||
break;
|
||||
case LV2_State_Status::LV2_STATE_ERR_BAD_TYPE:
|
||||
@@ -126,7 +128,8 @@ Lv2PluginState StateInterface::Save()
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
throw std::logic_error(SS("State save failed. " << e.what()));
|
||||
Lv2Log::debug(SS("State save failed. " << e.what()));
|
||||
return Lv2PluginState(); // an invalid state.
|
||||
}
|
||||
|
||||
return std::move(callState.state);
|
||||
|
||||
@@ -68,3 +68,4 @@ json_enum_converter<Units> *get_units_enum_converter();
|
||||
|
||||
} // namespace.
|
||||
|
||||
|
||||
|
||||