v1.1.20 Release

This commit is contained in:
Robin Davies
2023-04-20 15:27:18 -04:00
parent e2b62da073
commit 37426d9047
117 changed files with 2147 additions and 711 deletions
+19 -1
View File
@@ -349,7 +349,6 @@ private:
audioDriver->Close();
StopReaderThread();
pHost->GetHostWorkerThread()->Close();
// release any pdealboards owned by the process thread.
@@ -1164,6 +1163,21 @@ public:
RealtimeNextMidiProgramRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
} else if (command == RingBufferCommand::Lv2ErrorMessage)
{
size_t size;
int64_t instanceId;
hostReader.read(&instanceId);
hostReader.read(&size);
if (this->atomBuffer.size() < size+1)
{
this->atomBuffer.resize(size+1);
}
hostReader.read(size, &(atomBuffer[0]));
char *p = (char*)&(atomBuffer[0]);
p[size] = 0;
std::string message(p);
pNotifyCallbacks->OnNotifyLv2RealtimeError(instanceId,message);
}
else
{
@@ -1243,6 +1257,10 @@ public:
this->inputRingBuffer.reset();
this->outputRingBuffer.reset();
this->hostReader.Reset();
this->hostWriter.Reset();
this->realtimeReader.Reset();
this->realtimeWriter.Reset();
this->channelSelection = channelSelection;
+2
View File
@@ -154,6 +154,7 @@ public:
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
};
@@ -195,6 +196,7 @@ public:
virtual void SetListenForAtomOutput(bool listen) = 0;
virtual void UpdatePluginStates(Pedalboard& pedalboard) = 0;
virtual void UpdatePluginState(PedalboardItem& pedalboardItem) = 0;
virtual void Open(const JackServerSettings&jackServerSettings,const JackChannelSelection & channelSelection) = 0;
+4
View File
@@ -141,6 +141,8 @@ else()
endif()
set (PIPEDAL_SOURCES
MimeTypes.cpp MimeTypes.hpp
inverting_mutex.hpp
DbDezipper.hpp DbDezipper.cpp
WebServerLog.hpp
@@ -277,6 +279,8 @@ target_link_libraries(pipedald PRIVATE
#################################
add_executable(pipedaltest testMain.cpp
InvertingMutexTest.cpp
jsonTest.cpp
json_variant.cpp
json_variant.hpp
+3
View File
@@ -59,5 +59,8 @@ namespace pipedal {
virtual bool IsVst3() const = 0;
virtual bool GetLv2State(Lv2PluginState*state) = 0;
virtual bool HasErrorMessage() const = 0;
virtual const char*TakeErrorMessage() = 0;
};
} //namespace
+1
View File
@@ -49,5 +49,6 @@ namespace pipedal {
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread() = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
};
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) 2022 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 "json.hpp"
#include "json_variant.hpp"
#include <concepts>
#include <type_traits>
#include "inverting_mutex.hpp"
#include <iostream>
#include <thread>
#include <functional>
#include "util.hpp"
using namespace pipedal;
using namespace std;
using namespace std::chrono;
TEST_CASE("inverting_mutext test", "[inverting_mutex_test]")
{
inverting_mutex mutex;
{
std::thread thread(
[&mutex]() mutable
{
nice(5);
{
std::lock_guard<inverting_mutex> lock(mutex);
SetThreadName("imTest");
cout << "Thread holds lock" << endl;
std::this_thread::sleep_for(3s);
std::this_thread::sleep_for(1s);
cout << "Thread releases lock" << endl;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<inverting_mutex> lock(mutex); // cause the thread to avoid priority-inversion.
cout << "Main resumed." << endl;
}
{
std::lock_guard<inverting_mutex> lock(mutex);
}
thread.join();
}
}
+33 -22
View File
@@ -50,29 +50,38 @@ int LogFeature::vprintf(LV2_URID type,const char*fmt, va_list va)
{
std::lock_guard<std::mutex> guard(logMutex);
const char* prefix = "";
char buffer[1024];
int result = vsnprintf(buffer, sizeof(buffer), fmt, va);
int result = 0;
if (this->logMessageListener)
{
const char* prefix = "";
char buffer[1024];
strcpy(buffer,messagePrefix.c_str());
char *p = buffer+messagePrefix.length();
if (type == uris.ridError)
{
Lv2Log::error(buffer);
result = vsnprintf(p, sizeof(buffer)-messagePrefix.length(), fmt, va);
buffer[sizeof(buffer)-1] = '\0';
if (type == uris.ridError)
{
logMessageListener->OnLogError(buffer);
}
else if (type == uris.ridWarning)
{
logMessageListener->OnLogWarning(buffer);
}
else if (type == uris.ridNote)
{
logMessageListener->OnLogInfo(buffer);
}
else if (type == uris.ridTrace)
{
logMessageListener->OnLogDebug(buffer);
}
else {
logMessageListener->OnLogInfo(buffer);
}
}
else if (type == uris.ridWarning)
{
Lv2Log::warning(buffer);
}
else if (type == uris.ridNote)
{
Lv2Log::info(buffer);
}
else if (type == uris.ridTrace)
{
Lv2Log::debug(buffer);
}
else {
Lv2Log::info(buffer);
}
return result;
}
@@ -85,9 +94,11 @@ LogFeature::LogFeature()
log.printf = printfFn;
log.vprintf = vprintfFn;
}
void LogFeature::Prepare(MapFeature*map)
void LogFeature::Prepare(MapFeature*map, const std::string &messagePrefix, LogMessageListener*listener)
{
uris.Map(map);
this->messagePrefix = messagePrefix;
this->logMessageListener = listener;
}
+13 -2
View File
@@ -35,8 +35,17 @@
namespace pipedal {
class LogFeature {
public:
class LogMessageListener {
public:
virtual void OnLogError(const char*message) = 0;
virtual void OnLogWarning(const char*message) = 0;
virtual void OnLogInfo(const char*message) = 0;
virtual void OnLogDebug(const char*message) = 0;
};
private:
LogMessageListener *logMessageListener = nullptr;
std::string messagePrefix;
LV2_URID nextAtom = 0;
LV2_Feature feature;
LV2_Log_Log log;
@@ -59,7 +68,8 @@ namespace pipedal {
public:
LogFeature();
void Prepare(MapFeature* map);
void Prepare(MapFeature* map, const std::string &messagePrefix, LogMessageListener*listener);
void LogError(const char*fmt,...);
void LogWarning(const char*fmt,...);
@@ -84,5 +94,6 @@ namespace pipedal {
int vprintf(LV2_URID type, const char* fmt, va_list va);
};
}
+28 -1
View File
@@ -54,6 +54,7 @@ Lv2Effect::Lv2Effect(
{
auto pWorld = pHost_->getWorld();
logFeature.Prepare(&(pHost_->GetMapFeature()),info_->name() + ": ",this);
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
this->bypass = pedalboardItem.isEnabled();
@@ -71,6 +72,8 @@ Lv2Effect::Lv2Effect(
LV2_Feature *const *features = pHost_->GetLv2Features();
this->features.push_back(logFeature.GetFeature());
for (auto p = features; *p != nullptr; ++p)
{
this->features.push_back(*p);
@@ -92,7 +95,7 @@ Lv2Effect::Lv2Effect(
}
this->features.push_back(nullptr);
LV2_Feature **myFeatures = &this->features[0];
const LV2_Feature **myFeatures = &this->features[0];
LilvInstance *pInstance = nullptr;
try {
@@ -676,4 +679,28 @@ bool Lv2Effect::GetLv2State(Lv2PluginState*state)
}
void Lv2Effect::OnLogError(const char*message)
{
// only errors get transmitted to the client.
strncpy(this->errorMessage,message,sizeof(errorMessage));
errorMessage[sizeof(errorMessage)-1] = '\0';
this->hasErrorMessage = true;
}
void Lv2Effect::OnLogWarning(const char*message)
{
Lv2Log::warning(message);
}
void Lv2Effect::OnLogInfo(const char*message)
{
Lv2Log::info(message);
}
void Lv2Effect::OnLogDebug(const char*message)
{
Lv2Log::debug(message);
}
+16 -5
View File
@@ -34,6 +34,7 @@
#include "lv2/atom.lv2/forge.h"
#include "AtomBuffer.hpp"
#include "StateInterface.hpp"
#include "LogFeature.hpp"
namespace pipedal
@@ -41,12 +42,19 @@ namespace pipedal
class RealtimeRingBufferWriter;
class Lv2Effect : public IEffect
class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
{
private:
virtual void OnLogError(const char*message);
virtual void OnLogWarning(const char*message);
virtual void OnLogInfo(const char*message);
virtual void OnLogDebug(const char*message);
private:
std::unique_ptr<StateInterface> stateInterface;
void RestoreState(const PedalboardItem&pedalboardItem);
LogFeature logFeature;
std::map<std::string,AtomBuffer> patchPropertyPrototypes;
IHost *pHost = nullptr;
@@ -74,7 +82,7 @@ namespace pipedal
std::vector<char *> inputAtomBuffers;
std::vector<char *> outputAtomBuffers;
std::vector<LV2_Feature *> features;
std::vector<const LV2_Feature *> features;
LV2_Feature *work_schedule_feature = nullptr;
virtual std::string GetUri() const { return info->uri(); }
@@ -169,6 +177,7 @@ namespace pipedal
void BypassTo(float value);
public:
virtual bool GetLv2State(Lv2PluginState*state);
virtual void RequestPatchProperty(LV2_URID uridUri);
virtual void SetPatchProperty(LV2_URID uridUri,size_t size, LV2_Atom*value);
@@ -187,7 +196,8 @@ namespace pipedal
}
bool hasErrorMessage = false;
char errorMessage[1024];
public:
Lv2Effect(
IHost *pHost,
@@ -195,7 +205,8 @@ namespace pipedal
const PedalboardItem &pedalboardItem);
~Lv2Effect();
bool HasErrorMessage() const { return this->hasErrorMessage; }
const char*TakeErrorMessage() { this->hasErrorMessage = false; return this->errorMessage; }
virtual void ResetAtomBuffers();
virtual uint64_t GetInstanceId() const { return instanceId; }
+23 -5
View File
@@ -46,6 +46,8 @@ std::vector<float *> Lv2Pedalboard::AllocateAudioBuffers(int nChannels)
return result;
}
int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbol)
{
for (int i = 0; i < realtimeEffects.size(); ++i)
@@ -60,7 +62,9 @@ int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbo
}
std::vector<float *> Lv2Pedalboard::PrepareItems(
std::vector<PedalboardItem> &items,
std::vector<float *> inputBuffers)
std::vector<float *> inputBuffers,
Lv2PedalboardErrorList&errorList
)
{
for (int i = 0; i < items.size(); ++i)
{
@@ -84,8 +88,8 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
this->processActions.push_back(preMixAction);
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs);
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs);
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs,errorList);
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs,errorList);
this->processActions.push_back(
[pSplit](uint32_t frames)
@@ -113,6 +117,12 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
{
Lv2Log::warning(SS(e.what()));
}
if (pLv2Effect->HasErrorMessage())
{
std::string error = pLv2Effect->TakeErrorMessage();
Lv2Log::error(error);
errorList.push_back({item.instanceId(), error});
}
if (pLv2Effect)
{
@@ -206,7 +216,7 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
return inputBuffers;
}
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList)
{
this->pHost = pHost;
@@ -224,7 +234,7 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard)
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers);
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers,errorList);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
if (nOutputs == 1)
{
@@ -396,6 +406,14 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
{
processActions[i](samples);
}
for (size_t i = 0; i < this->effects.size(); ++i)
{
IEffect* effect = effects[i].get();
if (effect->HasErrorMessage())
{
ringBufferWriter->WriteLv2ErrorMessage(effect->GetInstanceId(),effect->TakeErrorMessage());
}
}
for (size_t i = 0; i < samples; ++i)
{
float volume = outputVolume.Tick();
+13 -2
View File
@@ -35,6 +35,16 @@ class RealtimeVuBuffers;
class RealtimePatchPropertyRequest;
class RealtimeRingBufferWriter;
struct Lv2PedalboardError {
int64_t intanceId;
std::string message;
};
class Lv2PedalboardErrorList: public std::vector<Lv2PedalboardError> // (forward declaration issues with a using statement)
{
};
class Lv2Pedalboard {
IHost *pHost = nullptr;
@@ -86,7 +96,8 @@ class Lv2Pedalboard {
std::vector<float*> PrepareItems(
std::vector<PedalboardItem> & items,
std::vector<float*> inputBuffers
std::vector<float*> inputBuffers,
Lv2PedalboardErrorList &errorList
);
void PrepareMidiMap(const Pedalboard&pedalboard);
@@ -99,7 +110,7 @@ public:
Lv2Pedalboard() { }
~Lv2Pedalboard() { }
void Prepare(IHost *pHost,Pedalboard&pedalboard);
void Prepare(IHost *pHost,Pedalboard&pedalboard, Lv2PedalboardErrorList &errorList);
std::vector<IEffect* > GetEffects() { return realtimeEffects; }
+23
View File
@@ -36,10 +36,15 @@ MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath)
lv2_state_make_path.handle = (LV2_State_Make_Path_Handle*)this;
lv2_state_make_path.path = FnAbsolutePath;
lv2_state_free_path.handle = (LV2_State_Free_Path_Handle*)this;
lv2_state_free_path.free_path = FnFreePath;
mapPathFeature.URI = LV2_STATE__mapPath;
mapPathFeature.data = (void*)&lv2_state_map_path;
makePathFeature.URI = LV2_STATE__makePath;
makePathFeature.data = (void*)&lv2_state_make_path;
freePathFeature.URI = LV2_STATE__freePath;
freePathFeature.data = (void*)&lv2_state_free_path;
}
void MapPathFeature::Prepare(MapFeature* map)
@@ -47,6 +52,16 @@ void MapPathFeature::Prepare(MapFeature* map)
}
void MapPathFeature::FreePath(char *path)
{
free(path);
}
void MapPathFeature::FnFreePath(LV2_State_Free_Path_Handle handle, char* path)
{
((MapPathFeature*)handle)->FreePath(path);
}
/*static*/ char *MapPathFeature::FnAbsolutePath(
LV2_State_Map_Path_Handle handle,
@@ -57,6 +72,10 @@ void MapPathFeature::Prepare(MapFeature* map)
char *MapPathFeature::AbsolutePath(const char *abstract_path)
{
if (strlen(abstract_path) == 0)
{
return strdup("");
}
std::filesystem::path t (abstract_path);
if (t.is_absolute()) {
return strdup(abstract_path);
@@ -75,6 +94,10 @@ char *MapPathFeature::FnAbstractPath(
char *MapPathFeature::AbstractPath(const char *absolute_path)
{
if (strlen(absolute_path) == 0)
{
return strdup("");
}
if (strncmp(storagePath.c_str(),absolute_path,storagePath.length()) == 0)
{
const char*result = absolute_path + storagePath.length();
+9
View File
@@ -32,16 +32,25 @@ namespace pipedal
void SetPluginStoragePath(const std::filesystem::path&path) { storagePath = path;}
const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;}
const LV2_Feature*GetMakePathFeature() { return &makePathFeature;}
const LV2_Feature*GetFreePathFeature() { return &freePathFeature;}
private:
char *AbsolutePath(const char *abstract_path);
static char *FnAbsolutePath(LV2_State_Map_Path_Handle handle,
const char *abstract_path);
LV2_State_Map_Path lv2_state_map_path;
LV2_State_Make_Path lv2_state_make_path;
LV2_State_Free_Path lv2_state_free_path;
void FreePath(char *path);
static void FnFreePath(LV2_State_Free_Path_Handle handle, char* path);
LV2_Feature mapPathFeature;
LV2_Feature makePathFeature;
LV2_Feature freePathFeature;
char *AbstractPath(const char *abstract_path);
static char * FnAbstractPath(
+160
View File
@@ -0,0 +1,160 @@
// Copyright (c) 2023 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 "MimeTypes.hpp"
using namespace pipedal;
void MimeTypes::MaybeInitialize()
{
if (!initialized)
{
initialized = true;
AddMimeType("MP3", "audio/mpeg");
AddMimeType("MPGA", "audio/mpeg");
AddMimeType("M4A", "audio/mp4");
AddMimeType("WAV", "audio/x-wav");
AddMimeType("WAV", "audio/wav");
AddMimeType("AMR", "audio/amr");
AddMimeType("AWB", "audio/amr-wb");
AddMimeType("WMA", "audio/x-ms-wma");
AddMimeType("OGG", "audio/ogg");
AddMimeType("OGG", "application/ogg");
AddMimeType("OGA", "application/ogg");
AddMimeType("AAC", "audio/aac");
AddMimeType("AAC", "audio/aac-adts");
AddMimeType("MKA", "audio/x-matroska");
AddMimeType("MID", "audio/midi");
AddMimeType("MIDI", "audio/midi");
AddMimeType("XMF", "audio/midi");
AddMimeType("RTTTL", "audio/midi");
AddMimeType("SMF", "audio/sp-midi");
AddMimeType("IMY", "audio/imelody");
AddMimeType("RTX", "audio/midi");
AddMimeType("OTA", "audio/midi");
AddMimeType("MXMF", "audio/midi");
AddMimeType("MPEG", "video/mpeg");
AddMimeType("MPG", "video/mpeg");
AddMimeType("MP4", "video/mp4");
AddMimeType("M4V", "video/mp4");
AddMimeType("3GP", "video/3gpp");
AddMimeType("3GPP", "video/3gpp");
AddMimeType("3G2", "video/3gpp2");
AddMimeType("3GPP2", "video/3gpp2");
AddMimeType("MKV", "video/x-matroska");
AddMimeType("WEBM", "video/webm");
AddMimeType("TS", "video/mp2ts");
AddMimeType("AVI", "video/avi");
AddMimeType("WMV", "video/x-ms-wmv");
AddMimeType("ASF", "video/x-ms-asf");
AddMimeType("JPG", "image/jpeg");
AddMimeType("JPEG", "image/jpeg");
AddMimeType("GIF", "image/gif");
AddMimeType("PNG", "image/png");
AddMimeType("BMP", "image/x-ms-bmp");
AddMimeType("WBMP", "image/vnd.wap.wbmp");
AddMimeType("WEBP", "image/webp");
AddMimeType("M3U", "audio/x-mpegurl");
AddMimeType("M3U", "application/x-mpegurl");
AddMimeType("PLS", "audio/x-scpls");
AddMimeType("WPL", "application/vnd.ms-wpl");
AddMimeType("M3U8", "application/vnd.apple.mpegurl");
AddMimeType("M3U8", "audio/mpegurl");
AddMimeType("M3U8", "audio/x-mpegurl");
AddMimeType("FL", "application/x-android-drm-fl");
AddMimeType("TXT", "text/plain");
AddMimeType("HTM", "text/html");
AddMimeType("HTML", "text/html");
AddMimeType("PDF", "application/pdf");
AddMimeType("DOC", "application/msword");
AddMimeType("XLS", "application/vnd.ms-excel");
AddMimeType("PPT", "application/mspowerpoint");
AddMimeType("FLAC", "audio/x-flac");
AddMimeType("FLAC", "audio/flac");
AddMimeType("ZIP", "application/zip");
AddMimeType("MPG", "video/mp2p");
AddMimeType("MPEG", "video/mp2p");
}
}
static MimeTypes staticContruct;
static std::string empty;
const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension)
{
MaybeInitialize();
auto iter = extensionToMimeType.find(extension);
if (iter == extensionToMimeType.end()) return empty;
return iter->second;
}
const std::string& MimeTypes::ExtensionFromMimeType(const std::string &mimeType)
{
MaybeInitialize();
auto iter = mimeTypeToExtension.find(mimeType);
if (iter == mimeTypeToExtension.end()) return empty;
return iter->second;
}
static std::string toLower(const std::string&value)
{
std::string result;
result.resize(value.length());
for (size_t i = 0; i < value.length(); ++i)
{
char c = value[i];
if (c >= 'A' && c <= 'Z')
{
c += 'a'-'A';
}
result[i] = c;
}
return result;
}
void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType)
{
std::string extension = "." + toLower(extension_);
mimeTypeToExtension[mimeType] = extension;
extensionToMimeType[extension] = mimeType;
if (mimeType.starts_with("audio/"))
{
audioExtensions.insert(extension);
}
if (mimeType.starts_with("video/"))
{
videoExtensions.insert(extension);
}
}
std::map<std::string,std::string> MimeTypes::mimeTypeToExtension;
std::map<std::string,std::string> MimeTypes::extensionToMimeType;
std::set<std::string> MimeTypes::audioExtensions;
std::set<std::string> MimeTypes::videoExtensions;
bool MimeTypes::initialized;
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2023 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.
#pragma once
#include <string>
#include <map>
#include <set>
namespace pipedal {
class MimeTypes {
public:
static const std::string& MimeTypeFromExtension(const std::string &extension);
static const std::string& ExtensionFromMimeType(const std::string &mimeType);
private:
static void AddMimeType(const std::string&extension, const std::string&mimeType);
static std::map<std::string,std::string> mimeTypeToExtension;
static std::map<std::string,std::string> extensionToMimeType;
static std::set<std::string> audioExtensions;
static std::set<std::string> videoExtensions;
static void MaybeInitialize();
static bool initialized;
};
}
+1
View File
@@ -199,6 +199,7 @@ 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,stateUpdateCount)
JSON_MAP_REFERENCE(PedalboardItem,lv2State)
JSON_MAP_END()
+2
View File
@@ -85,6 +85,7 @@ class PedalboardItem: public JsonMemberWritable {
std::vector<PedalboardItem> bottomChain_;
std::vector<MidiBinding> midiBindings_;
std::string vstState_;
uint32_t stateUpdateCount_ = 0;
Lv2PluginState lv2State_;
public:
ControlValue*GetControlValue(const std::string&symbol);
@@ -101,6 +102,7 @@ public:
GETTER_SETTER_VEC(topChain)
GETTER_SETTER_VEC(bottomChain)
GETTER_SETTER_VEC(midiBindings)
GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State)
+60 -18
View File
@@ -236,7 +236,7 @@ void PiPedalModel::Load()
if (jackServerSettings.IsValid())
{
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
if (jackServerSettings.IsValid())
if (!jackServerSettings.IsValid())
{
for (size_t retry = 0; retry < 5; ++retry)
{
@@ -267,11 +267,11 @@ void PiPedalModel::Load()
try
{
audioHost->Open(this->jackServerSettings, selection);
bool loadedSuccessfully = false;
try
{
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)};
this->lv2Pedalboard = lv2Pedalboard;
audioHost->SetPedalboard(lv2Pedalboard);
LoadCurrentPedalboard();
loadedSuccessfully = true;
}
catch (const std::exception &e)
{
@@ -360,8 +360,15 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s
void PiPedalModel::OnNotifyLv2StateChanged(uint64_t instanceId)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
SetPresetChanged(-1, true);
PedalboardItem *item = pedalboard.GetItem(instanceId);
if (item != nullptr)
{
item->stateUpdateCount(item->stateUpdateCount()+1);
this->audioHost->UpdatePluginState(*item);
this->FirePedalboardChanged(-1,false);
this->SetPresetChanged(-1, true);
}
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
@@ -503,7 +510,7 @@ void PiPedalModel::FireBanksChanged(int64_t clientId)
delete[] t;
}
void PiPedalModel::FirePedalboardChanged(int64_t clientId)
void PiPedalModel::FirePedalboardChanged(int64_t clientId,bool loadAudioThread)
{
// noify subscribers.
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
@@ -518,14 +525,16 @@ void PiPedalModel::FirePedalboardChanged(int64_t clientId)
}
delete[] t;
// notify the audio thread.
if (audioHost->IsOpen())
if (loadAudioThread)
{
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)};
this->lv2Pedalboard = lv2Pedalboard;
audioHost->SetPedalboard(lv2Pedalboard);
UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
// notify the audio thread.
if (audioHost->IsOpen())
{
LoadCurrentPedalboard();
UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
}
}
}
void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
@@ -1148,9 +1157,10 @@ void PiPedalModel::RestartAudio()
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)};
this->lv2Pedalboard = lv2Pedalboard;
audioHost->SetPedalboard(lv2Pedalboard);
std::vector<std::string> errorMessages;
LoadCurrentPedalboard();
this->UpdateRealtimeVuSubscriptions();
UpdateRealtimeMonitorPortSubscriptions();
}
@@ -1711,6 +1721,8 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(pluginInstanceId);
if (pedalboardItem != nullptr)
{
int32_t oldStateUpdateCount = pedalboardItem->stateUpdateCount();
PluginPresetValues presetValues = storage.GetPluginPresetValues(pedalboardItem->uri(), presetInstanceId);
// if the plugin has state, we have to rebuild the pedalboard, since setting state is not thread-safe.
@@ -1736,6 +1748,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
delete[] t;
} else {
pedalboardItem->lv2State(presetValues.state);
pedalboardItem->stateUpdateCount(oldStateUpdateCount+1);
FirePedalboardChanged(-1); // does a complete reload of both client and audio server.
}
this->SetPresetChanged(-1, true);
@@ -1985,5 +1998,34 @@ uint64_t PiPedalModel::CreateNewPreset()
return storage.CreateNewPreset();
}
bool PiPedalModel::LoadCurrentPedalboard()
{
Lv2PedalboardErrorList errorMessages;
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard,errorMessages)};
this->lv2Pedalboard = lv2Pedalboard;
// apply the error messages to the lv2Pedalboard.
// return true if the error messages have changed.
audioHost->SetPedalboard(lv2Pedalboard);
return true;
}
void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
// Notify clients.
size_t n = subscribers.size();
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[n];
for (size_t i = 0; i < n; ++i)
{
t[i] = this->subscribers[i];
}
for (size_t i = 0; i < n; ++i)
{
t[i]->OnErrorMessage(error);
}
delete[] t;
}
+6 -1
View File
@@ -76,6 +76,7 @@ namespace pipedal
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
virtual void OnErrorMessage(const std::string&message) = 0;
virtual void Close() = 0;
};
@@ -137,7 +138,7 @@ namespace pipedal
void SetPresetChanged(int64_t clientId, bool value);
void FirePresetsChanged(int64_t clientId);
void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalboardChanged(int64_t clientId);
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
void FireChannelSelectionChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
@@ -177,6 +178,8 @@ namespace pipedal
;
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest) override;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) override;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) override;
void UpdateVst3Settings(Pedalboard &pedalboard);
@@ -324,6 +327,8 @@ namespace pipedal
void DeleteSampleFile(const std::filesystem::path &fileName);
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,const std::string&fileBody);
uint64_t CreateNewPreset();
bool LoadCurrentPedalboard();
};
} // namespace pipedal.
+4
View File
@@ -1498,6 +1498,10 @@ private:
Send("onLv2StateChanged",instanceId);
}
virtual void OnErrorMessage(const std::string&message)
{
Send("onErrorMessage",message);
}
virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value)
{
PatchPropertyChangedBody body;
+54 -9
View File
@@ -25,6 +25,7 @@
#include "PiPedalUI.hpp"
#include "PluginHost.hpp"
#include "ss.hpp"
#include "MimeTypes.hpp"
using namespace pipedal;
@@ -65,7 +66,7 @@ PiPedalUI::PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::files
lilv_nodes_free(fileNodes);
}
PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
UiFileType::UiFileType(PluginHost *pHost, const LilvNode *node)
{
auto pWorld = pHost->getWorld();
@@ -93,7 +94,28 @@ PiPedalFileType::PiPedalFileType(PluginHost *pHost, const LilvNode *node)
}
else
{
throw std::logic_error("pipedal_ui:fileType is missing fileExtension property.");
this->fileExtension_ = "";
}
AutoLilvNode mimeType = lilv_world_get(
pWorld,
node,
pHost->lilvUris.pipedalUI__mimeType,
nullptr);
if (mimeType)
{
this->mimeType_ = mimeType.AsString();
}
if (fileExtension_ == "")
{
fileExtension_ = MimeTypes::ExtensionFromMimeType(mimeType_);
}
if (mimeType_ == "")
{
mimeType_ = MimeTypes::MimeTypeFromExtension(fileExtension_);
if (mimeType_ == "")
{
mimeType_ = "application/octet-stream";
}
}
}
UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath)
@@ -165,12 +187,12 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
this->portGroup_ = portGroup.AsUri();
}
this->fileTypes_ = PiPedalFileType::GetArray(pHost, node, pHost->lilvUris.pipedalUI__fileTypes);
this->fileTypes_ = UiFileType::GetArray(pHost, node, pHost->lilvUris.pipedalUI__fileTypes);
}
std::vector<PiPedalFileType> PiPedalFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
std::vector<UiFileType> UiFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
{
std::vector<PiPedalFileType> result;
std::vector<UiFileType> result;
LilvWorld *pWorld = pHost->getWorld();
LilvNodes *fileTypeNodes = lilv_world_find_nodes(pWorld, node, pHost->lilvUris.pipedalUI__fileTypes, nullptr);
@@ -179,7 +201,7 @@ std::vector<PiPedalFileType> PiPedalFileType::GetArray(PluginHost *pHost, const
const LilvNode *fileTypeNode = lilv_nodes_get(fileTypeNodes, i);
try
{
PiPedalFileType fileType = PiPedalFileType(pHost, fileTypeNode);
UiFileType fileType = UiFileType(pHost, fileTypeNode);
result.push_back(std::move(fileType));
}
catch (const std::exception &e)
@@ -313,6 +335,28 @@ PiPedalUI::PiPedalUI(std::vector<UiFileProperty::ptr> &&fileProperties)
this->fileProperties_ = std::move(fileProperties);
}
UiFileType::UiFileType(const std::string&label, const std::string &fileType)
: label_(label)
, fileExtension_(fileType)
{
if (fileType.starts_with('.'))
{
fileExtension_ = fileType;
mimeType_ = MimeTypes::MimeTypeFromExtension(fileType);
if (mimeType_ == "")
{
mimeType_ = "application/octet-stream";
}
} else {
fileExtension_ = MimeTypes::ExtensionFromMimeType(fileType); // (may be blank, esp. for audio/* and video/*.
mimeType_ = fileType;
}
if (mimeType_ == "*")
{
mimeType_ = "application/octet-stream";
}
}
JSON_MAP_BEGIN(UiPortNotification)
JSON_MAP_REFERENCE(UiPortNotification, portIndex)
JSON_MAP_REFERENCE(UiPortNotification, symbol)
@@ -320,9 +364,10 @@ JSON_MAP_REFERENCE(UiPortNotification, plugin)
JSON_MAP_REFERENCE(UiPortNotification, protocol)
JSON_MAP_END()
JSON_MAP_BEGIN(PiPedalFileType)
JSON_MAP_REFERENCE(PiPedalFileType, label)
JSON_MAP_REFERENCE(PiPedalFileType, fileExtension)
JSON_MAP_BEGIN(UiFileType)
JSON_MAP_REFERENCE(UiFileType, label)
JSON_MAP_REFERENCE(UiFileType, mimeType)
JSON_MAP_REFERENCE(UiFileType, fileExtension)
JSON_MAP_END()
JSON_MAP_BEGIN(UiFileProperty)
+13 -7
View File
@@ -45,6 +45,7 @@
#define PIPEDAL_UI__fileType PIPEDAL_UI_PREFIX "fileType"
#define PIPEDAL_UI__fileExtension PIPEDAL_UI_PREFIX "fileExtension"
#define PIPEDAL_UI__mimeType PIPEDAL_UI_PREFIX "mimeType"
#define PIPEDAL_UI__outputPorts PIPEDAL_UI_PREFIX "outputPorts"
#define PIPEDAL_UI__text PIPEDAL_UI_PREFIX "text"
@@ -55,21 +56,25 @@ namespace pipedal {
class PluginHost;
class PiPedalFileType {
class UiFileType {
private:
std::string label_;
std::string mimeType_;
std::string fileExtension_;
public:
PiPedalFileType() { }
PiPedalFileType(PluginHost*pHost, const LilvNode*node);
UiFileType() { }
UiFileType(PluginHost*pHost, const LilvNode*node);
UiFileType(const std::string&label, const std::string &fileType);
static std::vector<PiPedalFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
static std::vector<UiFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
const std::string& label() const { return label_;}
const std::string &fileExtension() const { return fileExtension_; }
const std::string &mimeType() const { return mimeType_; }
public:
DECLARE_JSON_MAP(PiPedalFileType);
DECLARE_JSON_MAP(UiFileType);
};
@@ -96,7 +101,7 @@ namespace pipedal {
std::string label_;
std::int64_t index_ = -1;
std::string directory_;
std::vector<PiPedalFileType> fileTypes_;
std::vector<UiFileType> fileTypes_;
std::string patchProperty_;
std::string portGroup_;
public:
@@ -111,7 +116,8 @@ namespace pipedal {
const std::string &directory() const { return directory_; }
const std::string&portGroup() const { return portGroup_; }
const std::vector<PiPedalFileType> &fileTypes() const { return fileTypes_; }
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
std::vector<UiFileType> &fileTypes() { return fileTypes_; }
const std::string &patchProperty() const { return patchProperty_; }
bool IsValidExtension(const std::string&extension) const;
+26 -32
View File
@@ -158,6 +158,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
pipedalUI__fileTypes = lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
pipedalUI__fileProperty = lilv_new_uri(pWorld, PIPEDAL_UI__fileProperty);
pipedalUI__fileExtension = lilv_new_uri(pWorld, PIPEDAL_UI__fileExtension);
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);
@@ -191,35 +192,13 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
patch__writable = lilv_new_uri(pWorld,LV2_PATCH__writable);
patch__readable = lilv_new_uri(pWorld,LV2_PATCH__readable);
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
}
void PluginHost::LilvUris::Free()
{
rdfs__Comment.Free();
port_logarithmic.Free();
port__display_priority.Free();
port_range_steps.Free();
integer_property_uri.Free();
enumeration_property_uri.Free();
core__toggled.Free();
portprops__not_on_gui_property_uri.Free();
midi__event.Free();
core__designation.Free();
portgroups__group.Free();
units__unit.Free();
atom__bufferType.Free();
presets__preset.Free();
rdfs__label.Free();
lv2core__symbol.Free();
lv2core__name.Free();
time_Position.Free();
time_barBeat.Free();
time_beatsPerMinute.Free();
time_speed.Free();
appliesTo.Free();
isA.Free();
}
static std::string nodeAsString(const LilvNode *node)
@@ -273,17 +252,15 @@ PluginHost::PluginHost()
{
pWorld = nullptr;
LV2_Feature **features = new LV2_Feature *[10];
lv2Features.push_back(mapFeature.GetMapFeature());
lv2Features.push_back(mapFeature.GetUnmapFeature());
logFeature.Prepare(&mapFeature);
lv2Features.push_back(logFeature.GetFeature());
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
mapPathFeature.Prepare(&mapFeature);
lv2Features.push_back(mapPathFeature.GetMapPathFeature());
lv2Features.push_back(mapPathFeature.GetMakePathFeature());
lv2Features.push_back(mapPathFeature.GetFreePathFeature());
lv2Features.push_back(optionsFeature.GetFeature());
lv2Features.push_back(nullptr);
@@ -633,9 +610,26 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
path = path.parent_path();
std::string lv2DirectoryName = path.filename().string();
// we have a valid path property!
auto fileProperty =
std::make_shared<UiFileProperty>(
strLabel,propertyUri.AsUri(),lv2DirectoryName);
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld,propertyUri,lv2Host->lilvUris.dc__format,nullptr);
LILV_FOREACH(nodes, i, dc_types)
{
AutoLilvNode dc_type = lilv_nodes_get(dc_types,i);
std::string fileType = dc_type.AsString();
std::string label = "";
fileProperty->fileTypes().push_back(UiFileType(label,fileType));
}
fileProperties.push_back(
std::make_shared<UiFileProperty>(
strLabel,propertyUri.AsUri(),lv2DirectoryName)
fileProperty
);
}
@@ -1129,12 +1123,12 @@ std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri)
return nullptr;
}
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard)
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard, Lv2PedalboardErrorList &errorMessages)
{
Lv2Pedalboard *pPedalboard = new Lv2Pedalboard();
try
{
pPedalboard->Prepare(this, pedalboard);
pPedalboard->Prepare(this, pedalboard,errorMessages);
return pPedalboard;
}
catch (const std::exception &e)
+9 -8
View File
@@ -23,10 +23,8 @@
#include <memory>
#include "json.hpp"
#include "PluginType.hpp"
#include "Pedalboard.hpp"
#include <lilv/lilv.h>
#include "MapFeature.hpp"
#include "LogFeature.hpp"
#include "OptionsFeature.hpp"
#include <filesystem>
#include <cmath>
@@ -49,6 +47,7 @@ namespace pipedal
// forward declarations
class Lv2Effect;
class Lv2Pedalboard;
class Lv2PedalboardErrorList;
class PluginHost;
class JackConfiguration;
class JackChannelSelection;
@@ -674,6 +673,7 @@ namespace pipedal
AutoLilvNode pipedalUI__fileTypes;
AutoLilvNode pipedalUI__fileExtension;
AutoLilvNode pipedalUI__mimeType;
AutoLilvNode pipedalUI__outputPorts;
AutoLilvNode pipedalUI__text;
@@ -696,6 +696,8 @@ namespace pipedal
AutoLilvNode patch__writable;
AutoLilvNode patch__readable;
AutoLilvNode dc__format;
};
LilvUris lilvUris;
@@ -715,7 +717,6 @@ namespace pipedal
std::vector<const LV2_Feature *> lv2Features;
MapFeature mapFeature;
LogFeature logFeature;
OptionsFeature optionsFeature;
MapPathFeature mapPathFeature;
@@ -747,19 +748,19 @@ namespace pipedal
public:
void LogError(const std::string &message)
{
logFeature.LogError("%s", message.c_str());
Lv2Log::error(message);
}
void LogWarning(const std::string &message)
{
logFeature.LogWarning("%s", message.c_str());
Lv2Log::warning(message);
}
void LogNote(const std::string &message)
{
logFeature.LogNote("%s", message.c_str());
Lv2Log::info(message);
}
void LogTrace(const std::string &message)
{
logFeature.LogTrace("%s", message.c_str());
Lv2Log::debug(message);
}
virtual LilvWorld *getWorld()
{
@@ -801,7 +802,7 @@ namespace pipedal
IHost *asIHost() { return this; }
virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard);
virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard,Lv2PedalboardErrorList &errorList);
void setSampleRate(double sampleRate)
{
+3 -2
View File
@@ -44,8 +44,8 @@ namespace pipedal
bool mlocked = false;
size_t ringBufferSize;
size_t ringBufferMask;
volatile int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition
volatile int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition
int64_t readPosition = 0; // volatile = ordering barrier wrt writePosition
int64_t writePosition = 0; // volatile = ordering barrier wrt/ readPosition
std::mutex mutex;
std::mutex writeMutex;
@@ -85,6 +85,7 @@ namespace pipedal
{
this->readPosition = 0;
this->writePosition = 0;
this->is_open = true;
cvRead.notify_all();
}
void close()
+10 -1
View File
@@ -62,6 +62,8 @@ namespace pipedal
Lv2StateChanged,
SetInputVolume,
SetOutputVolume,
Lv2ErrorMessage,
};
@@ -192,7 +194,7 @@ namespace pipedal
: ringBuffer(ringBuffer)
{
}
void Reset() { ringBuffer->reset(); }
// 0 -> ready. -1: timed out. -2: closing.
template <class Rep, class Period>
RingBufferStatus wait_for(const std::chrono::duration<Rep,Period>& timeout) {
@@ -287,6 +289,7 @@ namespace pipedal
{
}
void Reset() { ringBuffer->reset(); }
template <typename T>
void write(RingBufferCommand command, const T &value)
{
@@ -464,6 +467,12 @@ namespace pipedal
{
write(RingBufferCommand::EffectReplaced, pedalboard);
}
void WriteLv2ErrorMessage(int64_t instanceId, const char*message)
{
size_t length = strlen(message);
write(RingBufferCommand::Lv2ErrorMessage,instanceId,length,(uint8_t*)message);
}
};
typedef RingBufferReader<true, false> RealtimeRingBufferReader;
+2 -1
View File
@@ -296,7 +296,8 @@ namespace pipedal
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
virtual bool GetLv2State(Lv2PluginState*state) { return false; }
virtual bool HasErrorMessage() const { return false; }
const char* TakeErrorMessage() { return ""; }
virtual bool IsVst3() const { return false; }
public:
+2 -1
View File
@@ -255,11 +255,12 @@ void Lv2PluginStateEntry::read_json(json_reader &reader)
{
std::string v;
reader.read_member("value",&v);
value_.resize(v.length());
value_.resize(v.length()+1);
for (size_t i = 0; i < v.length(); ++i)
{
value_[i] = (uint8_t)v[i];
}
value_[v.length()] = 0;
} else if (atomType_ == LV2_ATOM__Float)
{
float v;
+27 -21
View File
@@ -82,15 +82,16 @@ LV2_Worker_Status Worker::WorkerRespond(uint32_t size, const void *data)
{
{
std::lock_guard lock(outstandingRequestMutex);
++outstandingRequests;
++outstandingResponses;
}
LV2_Worker_Status status;
if (responseRingBuffer.writeSpace() < sizeof(size) + size)
{
{
Lv2Log::warning(SS("LV2 Worker response too large: " << size << " bytes."));
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
cvOutstandingRequests.notify_all();
--outstandingResponses;
// no need to notify, because outstandingRequests will be decremented after return.
}
return LV2_WORKER_ERR_NO_SPACE;
}
@@ -123,7 +124,7 @@ bool Worker::EmitResponses()
responseRingBuffer.read(sizeof(size), (uint8_t *)&size);
if (size > responseBuffer.size())
{
responseBuffer.resize(size);
responseBuffer.resize(size); // allocation on the RT thread! But it's rare, and we have no choice.
}
uint8_t *pResponse = &(responseBuffer[0]);
@@ -132,29 +133,34 @@ bool Worker::EmitResponses()
workerInterface->work_response(lilvInstance->lv2_handle, size, pResponse);
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
--outstandingResponses;
cvOutstandingRequests.notify_all(); // must be done WITH the lock, since we may be destructed after the mutex is released.
}
cvOutstandingRequests.notify_all();
}
return emitted;
}
void Worker::WaitForAllResponses()
{
using Clock = std::chrono::steady_clock;
auto startTime = Clock::now();
while (true)
{
// can't do condition_variable::wait_until due to OS restrictions.
// instead, sleep briefly, waiting for wait tasks to complete.
bool gotResponse = EmitResponses();
{
std::unique_lock lock(outstandingRequestMutex);
if (outstandingRequests == 0)
if (outstandingRequests == 0 && outstandingResponses == 0)
{
break;
}
if (!gotResponse)
{
cvOutstandingRequests.wait(lock);
}
}
std::chrono::seconds waitDuration = std::chrono::duration_cast<std::chrono::seconds>(Clock::now()-startTime);
if (waitDuration.count() > 5) {
throw std::logic_error("Timed out waiting for a Worker task to complete.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
}
@@ -165,10 +171,6 @@ LV2_Worker_Status Worker::ScheduleWork(
{
std::lock_guard lock(outstandingRequestMutex);
++outstandingRequests;
if (exiting)
{
return LV2_WORKER_ERR_NO_SPACE;
}
}
LV2_Worker_Status status = this->pHostWorker->ScheduleWork(this, size, data);
if (status != LV2_Worker_Status::LV2_WORKER_SUCCESS)
@@ -176,8 +178,9 @@ LV2_Worker_Status Worker::ScheduleWork(
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
cvOutstandingRequests.notify_all(); // must be done WITH the lock!
return status;
}
cvOutstandingRequests.notify_all();
}
return status;
}
@@ -308,11 +311,14 @@ LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, co
void Worker::RunBackgroundTask(size_t size, uint8_t *data)
{
workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data);
bool notify = false;
{
std::lock_guard lock(outstandingRequestMutex);
--outstandingRequests;
std::lock_guard lock { this->outstandingRequestMutex};
--this->outstandingRequests;
if (this->outstandingRequests == 0)
{
this->cvOutstandingRequests.notify_all();
}
}
cvOutstandingRequests.notify_all();
}
+4 -2
View File
@@ -40,6 +40,7 @@
#include <thread>
#include "RingBuffer.hpp"
#include <memory>
#include "inverting_mutex.hpp"
namespace pipedal {
@@ -60,7 +61,7 @@ namespace pipedal {
RingBuffer<false,true> requestRingBuffer;
bool exiting = false;
std::mutex submitMutex;
inverting_mutex submitMutex;
std::vector<uint8_t> dataBuffer;
@@ -86,9 +87,10 @@ namespace pipedal {
LV2_Worker_Status WorkerRespond(uint32_t size,const void*data);
std::mutex outstandingRequestMutex;
inverting_mutex outstandingRequestMutex;
std::condition_variable cvOutstandingRequests;
int64_t outstandingRequests = 0;
int64_t outstandingResponses = 0;
void WaitForAllResponses();
public:
Worker(const std::shared_ptr<HostWorkerThread>& pHostWorker,LilvInstance *instance, const LV2_Worker_Interface *iface);
+194
View File
@@ -0,0 +1,194 @@
// Copyright (c) 2022 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.
/// @brief A mutex that handles priority-inversion.
/// The thread priority of a thread that holds the mutex is boosted to the highest priority of waiting threads, thereby avoiding priority inversion.
///
#ifdef WIN32
static_assert("Fix me!");
/// Windows has no such concept. The strategy will probably be to boost the priority of worker threads from
/// Nice(2) to something realtime, or work out a non-locking alternative.
/// Currently, the principle problem is the LV2 Worker thread (Pipedal project), which runs at nice(2) priority, which may cause priority inversions on
/// the realtime thread. Of some concern would be threads of BalancedConvolution (ToobAmp project). Longer convolution sections run below the
/// priority of the ALSA threads on linux, while shorter sections run above the priority of the ALSA thread. The convolution threads run
/// at high priority anyway, so priority inversion probably isn't a problem, even on Windows.
#endif
#include <pthread.h>
#include <stdexcept>
#include <string.h>
#include <chrono>
#include <ratio>
#include <condition_variable>
class inverting_mutex
{
public:
using native_handle_type = pthread_mutex_t *;
inverting_mutex()
{
pthread_mutexattr_t mta;
int rc = pthread_mutexattr_init(&mta);
if (rc != 0)
throw_system_error(rc);
rc = pthread_mutexattr_setprotocol(&mta, PTHREAD_PRIO_INHERIT);
if (rc != 0)
throw_system_error(rc);
rc = pthread_mutex_init(&mutex, &mta);
if (rc != 0)
throw_system_error(rc);
}
~inverting_mutex()
{
pthread_mutex_destroy(&mutex);
}
inverting_mutex(const inverting_mutex &) = delete;
inverting_mutex &operator=(const inverting_mutex &) = delete;
void
lock()
{
int e = pthread_mutex_lock(&mutex);
// EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
if (e)
throw_system_error(e);
}
bool
try_lock() noexcept
{
// XXX EINVAL, EAGAIN, EBUSY
int rc = pthread_mutex_trylock(&mutex);
switch (rc)
{
case 0:
return true;
case EBUSY:
return false;
default:
throw_system_error(rc);
return false;
}
}
template <class Rep, class Period>
bool
try_lock_for(const std::chrono::duration<Rep, Period> &rtime)
{
using clock = std::chrono::steady_clock;
auto rt = std::chrono::duration_cast<clock::duration>(rtime);
if (std::ratio_greater<clock::period, Period>())
++rt;
auto t = clock::now() + rt;
return try_lock_until(t);
}
template <class Clock, class Duration>
bool
try_lock_until(const std::chrono::time_point<Clock, Duration> &atime)
{
auto s = std::chrono::time_point_cast<std::chrono::seconds>(atime);
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(atime - s);
timespec ts = {
static_cast<std::time_t>(s.time_since_epoch().count()),
static_cast<long>(ns.count())};
return !pthread_mutex_timedlock(&mutex, &ts);
}
// template <class Rep, class Period>
// std::cv_status cond_wait_for(std::condition_variable &cond, const std::chrono::duration<Rep, Period> &rtime)
// {
// using clock = std::chrono::steady_clock;
// auto rt = std::chrono::duration_cast<clock::duration>(rtime);
// if (std::ratio_greater<clock::period, Period>())
// ++rt;
// auto t = clock::now() + rt;
// return cont_wait_until(cond,t);
// }
// template <class Clock, class Duration>
// std::cv_status cont_wait_until(std::condition_variable &cond,const std::chrono::time_point<Clock, Duration> &atime)
// {
// auto s = std::chrono::time_point_cast<std::chrono::seconds>(atime);
// auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(atime - s);
// auto now = Clock::now();
// auto sNow = std::chrono::time_point_cast<std::chrono::seconds>(now);
// auto nsNow = std::chrono::duration_cast<std::chrono::nanoseconds>(now - s);
// timespec tsNow = {
// static_cast<std::time_t>(sNow.time_since_epoch().count()),
// static_cast<long>(nsNow.count())};
// (void)tsNow;
// timespec ts = {
// static_cast<std::time_t>(s.time_since_epoch().count()),
// static_cast<long>(ns.count())};
// int rc = pthread_cond_timedwait(cond.native_handle(),this->native_handle(),&ts);
// switch (rc)
// {
// case 0:
// return std::cv_status::no_timeout;
// case ETIMEDOUT:
// return std::cv_status::timeout;
// default:
// throw_system_error(rc);
// return std::cv_status::timeout;
// }
// }
void
unlock()
{
// XXX EINVAL, EAGAIN, EBUSY
int rc = pthread_mutex_unlock(&mutex);
if (rc != 0)
{
throw_system_error(rc);
}
}
native_handle_type
native_handle() noexcept
{
return &mutex;
}
private:
void throw_system_error(int e)
{
throw std::logic_error(strerror(e));
}
pthread_mutex_t mutex;
};