v1.1.20 Release
This commit is contained in:
+19
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -49,5 +49,6 @@ namespace pipedal {
|
||||
virtual std::shared_ptr<HostWorkerThread> GetHostWorkerThread() = 0;
|
||||
|
||||
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user