Alsa device enumeration, Audio Device dialog tuning, minimum buffer size support (gx plugins).
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 Robin E. R. Davies
|
* Copyright (c) 2025 Robin E. R. Davies
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ namespace pipedal
|
|||||||
|
|
||||||
for (const auto &patchProperty : pathPatchProperties)
|
for (const auto &patchProperty : pathPatchProperties)
|
||||||
{
|
{
|
||||||
|
// yyy: only if the property changed!.
|
||||||
effect->SetPatchProperty(
|
effect->SetPatchProperty(
|
||||||
patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data());
|
patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ public:
|
|||||||
void Clear() {
|
void Clear() {
|
||||||
for (int i = 0; i < allocatedBuffers.size(); ++i)
|
for (int i = 0; i < allocatedBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
delete[] (char*)allocatedBuffers[i];
|
delete[] (char*)(allocatedBuffers[i]);
|
||||||
}
|
}
|
||||||
allocatedBuffers.resize(0);
|
allocatedBuffers.resize(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,11 +72,14 @@ namespace pipedal
|
|||||||
this->valid_ = true;
|
this->valid_ = true;
|
||||||
if (sampleRate_ == 0) sampleRate_ = 48000;
|
if (sampleRate_ == 0) sampleRate_ = 48000;
|
||||||
this->alsaDevice_ = "dummy:channels_2";
|
this->alsaDevice_ = "dummy:channels_2";
|
||||||
|
this->alsaInputDevice_ = "dummy:channels_2";
|
||||||
|
this->alsaOutputDevice_ = "dummy:channels_2";
|
||||||
}
|
}
|
||||||
bool IsDummyAudioDevice() const {
|
bool IsDummyAudioDevice() const {
|
||||||
return
|
return
|
||||||
this->alsaDevice_.starts_with("__DUMMY_AUDIO__")
|
this->alsaDevice_.starts_with("__DUMMY_AUDIO__")
|
||||||
|| this->alsaDevice_.starts_with("dummy:");
|
|| this->alsaDevice_.starts_with("dummy:")
|
||||||
|
|| this->alsaInputDevice_.starts_with("dummy:");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadJackDaemonConfiguration();
|
void ReadJackDaemonConfiguration();
|
||||||
|
|||||||
+504
-92
@@ -58,6 +58,28 @@ static fs::path makeAbsolutePath(const std::filesystem::path &path, const std::f
|
|||||||
return parentPath / path;
|
return parentPath / path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Lv2Effect::CheckStagingBufferSentries()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for (size_t i = 0; i < inputStagingBuffers.size(); ++i)
|
||||||
|
{
|
||||||
|
if (inputStagingBuffers[i].at(stagingBufferSize) != 99.9f)
|
||||||
|
{
|
||||||
|
throw std::logic_error("Staging buffer sentry overwritten.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < outputStagingBuffers.size(); ++i)
|
||||||
|
{
|
||||||
|
if (outputStagingBuffers[i].at(stagingBufferSize) != 99.9f)
|
||||||
|
{
|
||||||
|
throw std::logic_error("Staging buffer sentry overwritten.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
Lv2Effect::Lv2Effect(
|
Lv2Effect::Lv2Effect(
|
||||||
IHost *pHost_,
|
IHost *pHost_,
|
||||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||||
@@ -66,8 +88,12 @@ Lv2Effect::Lv2Effect(
|
|||||||
{
|
{
|
||||||
auto pWorld = pHost_->getWorld();
|
auto pWorld = pHost_->getWorld();
|
||||||
|
|
||||||
|
size_t stagedBufferSize = GetStagedBufferSize();
|
||||||
|
|
||||||
logFeature.Prepare(&pHost_->GetMapFeature(), info_->name() + ": ", this);
|
logFeature.Prepare(&pHost_->GetMapFeature(), info_->name() + ": ", this);
|
||||||
|
|
||||||
|
optionsFeature.Prepare(pHost->GetMapFeature(), 44100, stagedBufferSize, pHost->GetAtomBufferSize());
|
||||||
|
|
||||||
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
|
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
|
||||||
|
|
||||||
this->bypass = pedalboardItem.isEnabled();
|
this->bypass = pedalboardItem.isEnabled();
|
||||||
@@ -97,14 +123,15 @@ Lv2Effect::Lv2Effect(
|
|||||||
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
||||||
lv2_atom_forge_init(&inputForgeRt, map);
|
lv2_atom_forge_init(&inputForgeRt, map);
|
||||||
lv2_atom_forge_init(&outputForgeRt, map);
|
lv2_atom_forge_init(&outputForgeRt, map);
|
||||||
|
lv2_atom_forge_init(&stagedInputForgeRt, map);
|
||||||
|
|
||||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
||||||
|
|
||||||
// xxx: could we not stash the pPlugin in the plugin info?
|
// FIXME: could we not stash the pPlugin in the plugin info?
|
||||||
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
|
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
|
||||||
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
||||||
|
|
||||||
for (auto&port : info->ports())
|
for (auto &port : info->ports())
|
||||||
{
|
{
|
||||||
if (port->is_bypass())
|
if (port->is_bypass())
|
||||||
{
|
{
|
||||||
@@ -115,7 +142,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
lilv_node_free(uriNode);
|
lilv_node_free(uriNode);
|
||||||
{
|
{
|
||||||
AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin);
|
AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin);
|
||||||
char* bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
char *bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
||||||
|
|
||||||
std::string storagePath = pHost_->GetPluginStoragePath();
|
std::string storagePath = pHost_->GetPluginStoragePath();
|
||||||
|
|
||||||
@@ -123,7 +150,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
pHost_->GetMapFeature().GetMap(),
|
pHost_->GetMapFeature().GetMap(),
|
||||||
logFeature.GetLog(),
|
logFeature.GetLog(),
|
||||||
bundleUriString,
|
bundleUriString,
|
||||||
storagePath);
|
storagePath);
|
||||||
|
|
||||||
mapPathFeature.Prepare(&(pHost_->GetMapFeature()));
|
mapPathFeature.Prepare(&(pHost_->GetMapFeature()));
|
||||||
mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath());
|
mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath());
|
||||||
@@ -132,12 +159,10 @@ Lv2Effect::Lv2Effect(
|
|||||||
const auto &fileProperties = info_->piPedalUI()->fileProperties();
|
const auto &fileProperties = info_->piPedalUI()->fileProperties();
|
||||||
for (const auto &fileProperty : fileProperties)
|
for (const auto &fileProperty : fileProperties)
|
||||||
{
|
{
|
||||||
fs::path targetPath = fileProperty->directory() / std::filesystem::path(bundleUriString).parent_path().filename();
|
fs::path targetPath = fileProperty->directory() / std::filesystem::path(bundleUriString).parent_path().filename();
|
||||||
mapPathFeature.AddResourceFileMapping({
|
mapPathFeature.AddResourceFileMapping({bundleUriString,
|
||||||
bundleUriString,
|
storagePath / targetPath,
|
||||||
storagePath / targetPath,
|
fileProperty->fileTypes()});
|
||||||
fileProperty->fileTypes()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +171,16 @@ Lv2Effect::Lv2Effect(
|
|||||||
|
|
||||||
LV2_Feature *const *features = pHost_->GetLv2Features();
|
LV2_Feature *const *features = pHost_->GetLv2Features();
|
||||||
|
|
||||||
this->features.push_back(logFeature.GetFeature());
|
|
||||||
|
|
||||||
for (auto p = features; *p != nullptr; ++p)
|
for (auto p = features; *p != nullptr; ++p)
|
||||||
{
|
{
|
||||||
this->features.push_back(*p);
|
if (strcmp((*p)->URI, LV2_LOG__log) != 0)
|
||||||
|
{ // ommit the host's LOG feature.
|
||||||
|
this->features.push_back(*p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this->features.push_back(logFeature.GetFeature());
|
||||||
|
this->features.push_back(optionsFeature.GetFeature());
|
||||||
|
|
||||||
this->features.push_back(mapPathFeature.GetMapPathFeature());
|
this->features.push_back(mapPathFeature.GetMapPathFeature());
|
||||||
this->features.push_back(mapPathFeature.GetMakePathFeature());
|
this->features.push_back(mapPathFeature.GetMakePathFeature());
|
||||||
this->features.push_back(mapPathFeature.GetFreePathFeature());
|
this->features.push_back(mapPathFeature.GetFreePathFeature());
|
||||||
@@ -173,7 +202,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
}
|
}
|
||||||
this->features.push_back(nullptr);
|
this->features.push_back(nullptr);
|
||||||
|
|
||||||
const LV2_Feature **myFeatures = &this->features[0];
|
const LV2_Feature **myFeatures = &this->features.at(0);
|
||||||
|
|
||||||
LilvInstance *pInstance = nullptr;
|
LilvInstance *pInstance = nullptr;
|
||||||
try
|
try
|
||||||
@@ -195,14 +224,14 @@ Lv2Effect::Lv2Effect(
|
|||||||
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
|
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||||
LV2_WORKER__interface);
|
LV2_WORKER__interface);
|
||||||
this->worker = std::make_unique<Worker>(workerThread, pInstance, worker_interface);
|
this->worker = std::make_unique<Worker>(workerThread, pInstance, worker_interface);
|
||||||
|
|
||||||
const LV2_State_Interface *state_interface =
|
const LV2_State_Interface *state_interface =
|
||||||
(const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance,
|
(const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||||
LV2_STATE__interface);
|
LV2_STATE__interface);
|
||||||
|
|
||||||
if (state_interface)
|
if (state_interface)
|
||||||
{
|
{
|
||||||
this->stateInterface = std::make_unique<StateInterface>(pHost, &(this->features[0]), pInstance, state_interface);
|
this->stateInterface = std::make_unique<StateInterface>(pHost, &(this->features.at(0)), pInstance, state_interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->instanceId = pedalboardItem.instanceId();
|
this->instanceId = pedalboardItem.instanceId();
|
||||||
@@ -235,7 +264,7 @@ Lv2Effect::Lv2Effect(
|
|||||||
int index = GetControlIndex(v.key());
|
int index = GetControlIndex(v.key());
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
this->controlValues[index] = v.value();
|
this->controlValues.at(index) = v.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,20 +351,20 @@ void Lv2Effect::ConnectControlPorts()
|
|||||||
int controlArrayLength = 0;
|
int controlArrayLength = 0;
|
||||||
for (int i = 0; i < info->ports().size(); ++i)
|
for (int i = 0; i < info->ports().size(); ++i)
|
||||||
{
|
{
|
||||||
if (info->ports()[i]->index() >= controlArrayLength)
|
if (info->ports().at(i)->index() >= controlArrayLength)
|
||||||
{
|
{
|
||||||
controlArrayLength = info->ports()[i]->index() + 1;
|
controlArrayLength = info->ports().at(i)->index() + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->realtimePortInfo.resize(controlArrayLength);
|
this->realtimePortInfo.resize(controlArrayLength);
|
||||||
for (int i = 0; i < info->ports().size(); ++i)
|
for (int i = 0; i < info->ports().size(); ++i)
|
||||||
{
|
{
|
||||||
const auto &port = info->ports()[i];
|
const auto &port = info->ports().at(i);
|
||||||
if (port->is_control_port())
|
if (port->is_control_port())
|
||||||
{
|
{
|
||||||
int index = port->index();
|
int index = port->index();
|
||||||
realtimePortInfo[index] = port.get();
|
realtimePortInfo.at(index) = port.get();
|
||||||
lilv_instance_connect_port(pInstance, i, &this->controlValues[index]);
|
lilv_instance_connect_port(pInstance, i, &this->controlValues.at(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,7 +377,7 @@ void Lv2Effect::PreparePortIndices()
|
|||||||
|
|
||||||
for (int i = 0; i < info->ports().size(); ++i)
|
for (int i = 0; i < info->ports().size(); ++i)
|
||||||
{
|
{
|
||||||
const auto &port = info->ports()[i];
|
const auto &port = info->ports().at(i);
|
||||||
|
|
||||||
int portIndex = port->index();
|
int portIndex = port->index();
|
||||||
if (port->is_audio_port())
|
if (port->is_audio_port())
|
||||||
@@ -386,17 +415,17 @@ void Lv2Effect::PreparePortIndices()
|
|||||||
controlIndex[port->symbol()] = portIndex;
|
controlIndex[port->symbol()] = portIndex;
|
||||||
if (port->is_input())
|
if (port->is_input())
|
||||||
{
|
{
|
||||||
this->isInputControlPort[portIndex] = true;
|
this->isInputControlPort.at(portIndex) = true;
|
||||||
this->defaultInputControlValues[portIndex] = port->default_value();
|
this->defaultInputControlValues.at(portIndex) = port->default_value();
|
||||||
if (port->trigger_property())
|
if (port->trigger_property())
|
||||||
{
|
{
|
||||||
this->isInputTriggerControlPort[portIndex] = true;
|
this->isInputTriggerControlPort.at(portIndex) = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t maxInputControlPort = isInputControlPort.size();
|
size_t maxInputControlPort = isInputControlPort.size();
|
||||||
while (maxInputControlPort != 0 && !isInputControlPort[maxInputControlPort - 1])
|
while (maxInputControlPort != 0 && !isInputControlPort.at(maxInputControlPort - 1))
|
||||||
{
|
{
|
||||||
--maxInputControlPort;
|
--maxInputControlPort;
|
||||||
}
|
}
|
||||||
@@ -406,6 +435,14 @@ void Lv2Effect::PreparePortIndices()
|
|||||||
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
||||||
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
||||||
outputAtomBuffers.resize(outputAtomPortIndices.size());
|
outputAtomBuffers.resize(outputAtomPortIndices.size());
|
||||||
|
|
||||||
|
if (RequiresBufferStaging())
|
||||||
|
{
|
||||||
|
EnableBufferStaging(
|
||||||
|
GetStagedBufferSize(),
|
||||||
|
this->GetNumberOfInputAudioBuffers(),
|
||||||
|
this->GetNumberOfOutputAudioBuffers());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
|
void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
|
||||||
@@ -425,20 +462,20 @@ void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
|
|||||||
outputMixBuffers.resize(outputAudioPortIndices.size());
|
outputMixBuffers.resize(outputAudioPortIndices.size());
|
||||||
for (size_t i = 0; i < outputMixBuffers.size(); ++i)
|
for (size_t i = 0; i < outputMixBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
outputMixBuffers[i].resize(maxBufferSize);
|
outputMixBuffers.at(i).resize(maxBufferSize);
|
||||||
}
|
}
|
||||||
// connect the plugin to the mix buffer instead of output buffer.
|
// connect the plugin to the mix buffer instead of output buffer.
|
||||||
for (size_t i = 0; i < outputAudioPortIndices.size(); ++i)
|
for (size_t i = 0; i < outputAudioPortIndices.size(); ++i)
|
||||||
{
|
{
|
||||||
int pluginIndex = this->outputAudioPortIndices[i];
|
int pluginIndex = this->outputAudioPortIndices.at(i);
|
||||||
lilv_instance_connect_port(this->pInstance, pluginIndex, outputMixBuffers[i].data());
|
lilv_instance_connect_port(this->pInstance, pluginIndex, outputMixBuffers.at(i).data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
||||||
{
|
{
|
||||||
this->inputAudioBuffers[index] = buffer;
|
this->inputAudioBuffers.at(index) = buffer;
|
||||||
|
|
||||||
if (borrowedEffect)
|
if (borrowedEffect)
|
||||||
{
|
{
|
||||||
@@ -449,8 +486,20 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
|||||||
|
|
||||||
if (inputAudioPortIndices.size() == inputAudioBuffers.size())
|
if (inputAudioPortIndices.size() == inputAudioBuffers.size())
|
||||||
{
|
{
|
||||||
int pluginIndex = this->inputAudioPortIndices[index];
|
if (stagingBufferSize != 0)
|
||||||
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
{
|
||||||
|
int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||||
|
if (index >= inputStagingBufferPointers.size())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid input staging buffer index.");
|
||||||
|
}
|
||||||
|
lilv_instance_connect_port(this->pInstance, pluginIndex, inputStagingBufferPointers.at(index));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||||
|
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -458,7 +507,7 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
|||||||
// // cases: 1->0, 1->1, 2->0, 2->1
|
// // cases: 1->0, 1->1, 2->0, 2->1
|
||||||
// if (index < inputAudioPortIndices.size())
|
// if (index < inputAudioPortIndices.size())
|
||||||
// {
|
// {
|
||||||
// int pluginIndex = this->inputAudioPortIndices[index];
|
// int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||||
// lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
// lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
@@ -492,7 +541,7 @@ void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
|
|||||||
|
|
||||||
void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
||||||
{
|
{
|
||||||
this->outputAudioBuffers[index] = buffer;
|
this->outputAudioBuffers.at(index) = buffer;
|
||||||
|
|
||||||
if (borrowedEffect)
|
if (borrowedEffect)
|
||||||
{
|
{
|
||||||
@@ -503,10 +552,25 @@ void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
|||||||
|
|
||||||
if (this->inputAudioPortIndices.size() != 0) // i.e. we're not mixing a zero-input control
|
if (this->inputAudioPortIndices.size() != 0) // i.e. we're not mixing a zero-input control
|
||||||
{
|
{
|
||||||
if ((size_t)index < this->outputAudioPortIndices.size())
|
if (this->stagingBufferSize != 0)
|
||||||
{
|
{
|
||||||
int pluginIndex = this->outputAudioPortIndices[index];
|
if ((size_t)index < this->outputStagingBufferPointers.size())
|
||||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
{
|
||||||
|
int pluginIndex = this->outputAudioPortIndices.at(index);
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, outputStagingBufferPointers.at(index));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("outputStagingBufferPointers index out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((size_t)index < this->outputAudioPortIndices.size())
|
||||||
|
{
|
||||||
|
int pluginIndex = this->outputAudioPortIndices.at(index);
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,6 +587,16 @@ int Lv2Effect::GetControlIndex(const std::string &key) const
|
|||||||
|
|
||||||
Lv2Effect::~Lv2Effect()
|
Lv2Effect::~Lv2Effect()
|
||||||
{
|
{
|
||||||
|
if (deleted)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
throw std::runtime_error("Deleted twice!");
|
||||||
|
} catch (const std::exception&e)
|
||||||
|
{
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted = true;
|
||||||
if (worker)
|
if (worker)
|
||||||
{
|
{
|
||||||
worker->Close();
|
worker->Close();
|
||||||
@@ -531,6 +605,7 @@ Lv2Effect::~Lv2Effect()
|
|||||||
if (activated)
|
if (activated)
|
||||||
{
|
{
|
||||||
Deactivate();
|
Deactivate();
|
||||||
|
activated = false;
|
||||||
}
|
}
|
||||||
if (pInstance)
|
if (pInstance)
|
||||||
{
|
{
|
||||||
@@ -540,7 +615,9 @@ Lv2Effect::~Lv2Effect()
|
|||||||
if (work_schedule_feature)
|
if (work_schedule_feature)
|
||||||
{
|
{
|
||||||
free(work_schedule_feature->data);
|
free(work_schedule_feature->data);
|
||||||
|
work_schedule_feature->data = nullptr;
|
||||||
free(work_schedule_feature);
|
free(work_schedule_feature);
|
||||||
|
work_schedule_feature = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,9 +633,11 @@ void Lv2Effect::Activate()
|
|||||||
if (this->bypassControlIndex == -1)
|
if (this->bypassControlIndex == -1)
|
||||||
{
|
{
|
||||||
this->BypassDezipperSet(this->bypass ? 1.0f : 0.0f);
|
this->BypassDezipperSet(this->bypass ? 1.0f : 0.0f);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this->BypassDezipperSet(1.0f);
|
this->BypassDezipperSet(1.0f);
|
||||||
this->controlValues[this->bypassControlIndex] = this->bypass ? 1.0f: 0.0f;
|
this->controlValues.at(this->bypassControlIndex) = this->bypass ? 1.0f : 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,21 +646,91 @@ void Lv2Effect::UpdateAudioPorts()
|
|||||||
// called on realtime thread to switch borrowed effects to the new buffer pointers.
|
// called on realtime thread to switch borrowed effects to the new buffer pointers.
|
||||||
if (borrowedEffect)
|
if (borrowedEffect)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
if (stagingBufferSize != 0)
|
||||||
{
|
{
|
||||||
int portIndex = this->inputAudioPortIndices[i];
|
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
||||||
if (GetAudioInputBuffer(i) != nullptr)
|
|
||||||
{
|
{
|
||||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioInputBuffer(i));
|
int portIndex = this->inputAudioPortIndices.at(i);
|
||||||
|
if (inputStagingBufferPointers.at(i) != nullptr)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, portIndex, inputStagingBufferPointers.at(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
||||||
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
|
||||||
{
|
|
||||||
int portIndex = this->outputAudioPortIndices[i];
|
|
||||||
if (GetAudioOutputBuffer(i) != nullptr)
|
|
||||||
{
|
{
|
||||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i));
|
int portIndex = this->outputAudioPortIndices.at(i);
|
||||||
|
if (outputStagingBufferPointers.at(i) != nullptr)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, portIndex, outputStagingBufferPointers.at(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
if (stagedInputAtomBufferPointer == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid astagedInputAtomBufferPointer");
|
||||||
|
}
|
||||||
|
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],stagedInputAtomBufferPointer);
|
||||||
|
} else {
|
||||||
|
auto atomInputBuffer = this->GetAtomInputBuffer(i);
|
||||||
|
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],atomInputBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < this->outputAtomPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
if (stagedOutputAtomBufferPointer == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid astagedOutputAtomBufferPointer");
|
||||||
|
}
|
||||||
|
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],stagedOutputAtomBufferPointer);
|
||||||
|
} else {
|
||||||
|
auto atomOutputBuffer = this->GetAtomOutputBuffer(i);
|
||||||
|
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],atomOutputBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
int portIndex = this->inputAudioPortIndices.at(i);
|
||||||
|
if (GetAudioInputBuffer(i) != nullptr)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, portIndex, GetAudioInputBuffer(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
int portIndex = this->outputAudioPortIndices.at(i);
|
||||||
|
if (GetAudioOutputBuffer(i) != nullptr)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
auto atomInputBuffer = this->GetAtomInputBuffer(i);
|
||||||
|
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],atomInputBuffer);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < this->outputAtomPortIndices.size(); ++i)
|
||||||
|
{
|
||||||
|
auto atomOutputBuffer = this->GetAtomOutputBuffer(i);
|
||||||
|
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],atomOutputBuffer);
|
||||||
|
}
|
||||||
|
// for (size_t i = 0; i < this->inputMidiPortIndices.size(); ++i)
|
||||||
|
// {
|
||||||
|
// auto midiInputBuffer = this->GetMidiInputBuffer(i);
|
||||||
|
// lilv_instance_connect_port(pInstance, inputMidiPortIndices[i],midiInputBuffer);
|
||||||
|
// }
|
||||||
|
// for (size_t i = 0; i < this->outputMidiPortIndices.size(); ++i)
|
||||||
|
// {
|
||||||
|
// auto midiOutputBuffer = this->GetMidiOutputBuffer(i);
|
||||||
|
// lilv_instance_connect_port(pInstance, outputMidiPortIndices[i],midiOutputBuffer);
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,7 +741,7 @@ void Lv2Effect::AssignUnconnectedPorts()
|
|||||||
{
|
{
|
||||||
if (GetAudioInputBuffer(i) == nullptr)
|
if (GetAudioInputBuffer(i) == nullptr)
|
||||||
{
|
{
|
||||||
int pluginIndex = this->inputAudioPortIndices[i];
|
int pluginIndex = this->inputAudioPortIndices.at(i);
|
||||||
|
|
||||||
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
||||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
@@ -606,7 +755,7 @@ void Lv2Effect::AssignUnconnectedPorts()
|
|||||||
if (GetAudioOutputBuffer(i) == nullptr)
|
if (GetAudioOutputBuffer(i) == nullptr)
|
||||||
{
|
{
|
||||||
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
||||||
int pluginIndex = this->outputAudioPortIndices[i];
|
int pluginIndex = this->outputAudioPortIndices.at(i);
|
||||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,24 +764,42 @@ void Lv2Effect::AssignUnconnectedPorts()
|
|||||||
{
|
{
|
||||||
if (GetAtomInputBuffer(i) == nullptr)
|
if (GetAtomInputBuffer(i) == nullptr)
|
||||||
{
|
{
|
||||||
int pluginIndex = this->inputAtomPortIndices[i];
|
int pluginIndex = this->inputAtomPortIndices.at(i);
|
||||||
|
|
||||||
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
||||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
if (stagedInputAtomBufferPointer && i == 0)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, stagedInputAtomBufferPointer);
|
||||||
|
ResetInputAtomBuffer((char *)(stagedInputAtomBufferPointer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
|
}
|
||||||
ResetInputAtomBuffer((char *)buffer);
|
ResetInputAtomBuffer((char *)buffer);
|
||||||
this->inputAtomBuffers[i] = (char *)buffer;
|
this->inputAtomBuffers.at(i) = (char *)buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < this->GetNumberOfOutputAtomPorts(); ++i)
|
for (int i = 0; i < this->GetNumberOfOutputAtomPorts(); ++i)
|
||||||
{
|
{
|
||||||
if (GetAtomOutputBuffer(i) == nullptr)
|
if (GetAtomOutputBuffer(i) == nullptr)
|
||||||
{
|
{
|
||||||
int pluginIndex = this->outputAtomPortIndices[i];
|
int pluginIndex = this->outputAtomPortIndices.at(i);
|
||||||
|
|
||||||
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
||||||
ResetOutputAtomBuffer((char *)buffer);
|
ResetOutputAtomBuffer((char *)buffer);
|
||||||
|
|
||||||
|
if (stagedOutputAtomBufferPointer && i == 0)
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, stagedOutputAtomBufferPointer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||||
this->outputAtomBuffers[i] = (char *)buffer;
|
this->outputAtomBuffers.at(i) = (char *)buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,21 +825,154 @@ static inline void CopyBuffer(float *restrict input, float *restrict output, uin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
size_t Lv2Effect::stageToOutput(size_t outputIndex, size_t nFrames)
|
||||||
{
|
{
|
||||||
// close off the atom input frame.
|
size_t thisTime = nFrames - outputIndex;
|
||||||
|
size_t stagedOutputAvailable = this->stagingBufferSize - this->stagingOutputIx;
|
||||||
|
if (stagedOutputAvailable < thisTime)
|
||||||
|
{
|
||||||
|
thisTime = stagedOutputAvailable;
|
||||||
|
}
|
||||||
|
if (thisTime)
|
||||||
|
{
|
||||||
|
for (size_t ch = 0; ch < this->GetNumberOfOutputAudioBuffers(); ++ch)
|
||||||
|
{
|
||||||
|
float *restrict pIn = this->outputStagingBufferPointers.at(ch) + this->stagingOutputIx;
|
||||||
|
float *restrict pOut = this->GetAudioOutputBuffer(ch) + outputIndex;
|
||||||
|
for (size_t i = 0; i < thisTime; ++i)
|
||||||
|
{
|
||||||
|
pOut[i] = pIn[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->stagingOutputIx += thisTime;
|
||||||
|
}
|
||||||
|
return outputIndex + thisTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::copyAtomBufferEventSequence(LV2_Atom_Sequence *controlInput, LV2_Atom_Forge &outputForge)
|
||||||
|
{
|
||||||
|
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||||
|
{
|
||||||
|
lv2_atom_forge_frame_time(&outputForge, ev->time.frames);
|
||||||
|
lv2_atom_forge_raw(&outputForge, &(ev->body), ev->body.size); // literal copy of the atom body.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Lv2Effect::stageToInput(size_t inputSampleOffset, size_t samples)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t thisTime = samples - inputSampleOffset;
|
||||||
|
size_t inputAvailable = this->stagingBufferSize - this->stagingInputIx;
|
||||||
|
if (thisTime > inputAvailable)
|
||||||
|
{
|
||||||
|
thisTime = inputAvailable;
|
||||||
|
}
|
||||||
|
// copy into staging buffers.
|
||||||
|
for (size_t nInput = 0; nInput < this->inputAudioBuffers.size(); ++nInput)
|
||||||
|
{
|
||||||
|
float *restrict pInput = this->inputAudioBuffers[nInput] + inputSampleOffset;
|
||||||
|
float *restrict pOutput = this->inputStagingBufferPointers.at(nInput) + this->stagingInputIx;
|
||||||
|
for (size_t i = 0; i < thisTime; ++i)
|
||||||
|
{
|
||||||
|
pOutput[i] = pInput[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->stagingInputIx += thisTime;
|
||||||
|
inputSampleOffset += thisTime;
|
||||||
|
|
||||||
|
if (stagingInputIx == this->stagingBufferSize)
|
||||||
|
{
|
||||||
|
// close off the atom input frame.
|
||||||
|
if (stagedInputAtomBufferPointer)
|
||||||
|
{
|
||||||
|
lv2_atom_forge_pop(&this->stagedInputForgeRt, &staged_input_frame);
|
||||||
|
}
|
||||||
|
if (stagedOutputAtomBufferPointer)
|
||||||
|
{
|
||||||
|
ResetOutputAtomBuffer((char *)stagedOutputAtomBufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
lilv_instance_run(pInstance, this->stagingBufferSize);
|
||||||
|
|
||||||
|
if (worker)
|
||||||
|
{
|
||||||
|
|
||||||
|
worker->EmitResponses();
|
||||||
|
}
|
||||||
|
if (stagedOutputAtomBufferPointer)
|
||||||
|
{
|
||||||
|
copyAtomBufferEventSequence((LV2_Atom_Sequence *)stagedOutputAtomBufferPointer, this->outputForgeRt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->stagingInputIx = 0;
|
||||||
|
this->stagingOutputIx = 0;
|
||||||
|
this->resetStagedInputAtomBuffer();
|
||||||
|
}
|
||||||
|
return inputSampleOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::resetStagedInputAtomBuffer()
|
||||||
|
{
|
||||||
|
if (stagedInputAtomBufferPointer)
|
||||||
|
{
|
||||||
|
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||||
|
lv2_atom_forge_set_buffer(
|
||||||
|
&(this->stagedInputForgeRt), (uint8_t *)(this->stagedInputAtomBufferPointer), notify_capacity);
|
||||||
|
lv2_atom_forge_sequence_head(&this->inputForgeRt, &staged_input_frame, urids.units__frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Lv2Effect::RunWithBufferStaging(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||||
|
{
|
||||||
|
// accumulte control input sequence until we can execute a run operation.
|
||||||
if (this->inputAtomBuffers.size() != 0)
|
if (this->inputAtomBuffers.size() != 0)
|
||||||
{
|
{
|
||||||
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
|
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
|
||||||
|
|
||||||
|
LV2_Atom_Sequence *controlInput = (LV2_Atom_Sequence *)GetAtomInputBuffer(0);
|
||||||
|
copyAtomBufferEventSequence(controlInput, this->stagedInputForgeRt);
|
||||||
}
|
}
|
||||||
|
// Prepare ACTUAL control output port.
|
||||||
lilv_instance_run(pInstance, samples);
|
if (this->stagedOutputAtomBufferPointer)
|
||||||
|
|
||||||
if (worker)
|
|
||||||
{
|
{
|
||||||
// relay worker response
|
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||||
worker->EmitResponses();
|
lv2_atom_forge_set_buffer(
|
||||||
|
&(this->outputForgeRt), (uint8_t *)(this->inputAtomBuffers.at(0)), notify_capacity);
|
||||||
|
lv2_atom_forge_sequence_head(&this->outputForgeRt, &output_frame, urids.units__frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t inputSampleOffset = 0;
|
||||||
|
uint32_t outputSampleOffset = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
outputSampleOffset = stageToOutput(outputSampleOffset, samples);
|
||||||
|
|
||||||
|
CheckStagingBufferSentries();
|
||||||
|
|
||||||
|
if (inputSampleOffset == samples)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
inputSampleOffset = stageToInput(inputSampleOffset, samples);
|
||||||
|
}
|
||||||
|
// no staging data avaialble? Output zeros.
|
||||||
|
if (outputSampleOffset != samples)
|
||||||
|
{
|
||||||
|
size_t thisTime = samples - outputSampleOffset;
|
||||||
|
for (size_t ch = 0; ch < this->GetNumberOfOutputAudioBuffers(); ++ch)
|
||||||
|
{
|
||||||
|
float *pOut = this->GetAudioOutputBuffer(ch) + outputSampleOffset;
|
||||||
|
for (size_t i = 0; i < thisTime; ++i)
|
||||||
|
{
|
||||||
|
pOut[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MixOutput(samples, realtimeRingBufferWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Lv2Effect::MixOutput(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||||
|
{
|
||||||
// for zero-input plugins, mix the plugin output with the input signal.
|
// for zero-input plugins, mix the plugin output with the input signal.
|
||||||
if (this->inputAudioPortIndices.size() == 0)
|
if (this->inputAudioPortIndices.size() == 0)
|
||||||
{
|
{
|
||||||
@@ -695,14 +995,14 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input = this->inputAudioBuffers[0];
|
input = this->inputAudioBuffers.at(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
input = this->inputAudioBuffers[i];
|
input = this->inputAudioBuffers.at(i);
|
||||||
}
|
}
|
||||||
float *restrict pluginOutput = this->outputMixBuffers[i].data();
|
float *restrict pluginOutput = this->outputMixBuffers.at(i).data();
|
||||||
float *restrict finalOutput = this->outputAudioBuffers[i];
|
float *restrict finalOutput = this->outputAudioBuffers.at(i);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < samples; ++i)
|
for (uint32_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
@@ -713,11 +1013,11 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
else if (this->outputAudioPortIndices.size() == 1 && this->outputAudioBuffers.size() == 2)
|
else if (this->outputAudioPortIndices.size() == 1 && this->outputAudioBuffers.size() == 2)
|
||||||
{
|
{
|
||||||
// 1 plugin output into 2 outputs.
|
// 1 plugin output into 2 outputs.
|
||||||
float *restrict pluginOutput = this->outputMixBuffers[0].data();
|
float *restrict pluginOutput = this->outputMixBuffers.at(0).data();
|
||||||
for (size_t i = 0; i < this->outputMixBuffers.size(); ++i)
|
for (size_t i = 0; i < this->outputMixBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
float *restrict input = this->inputAudioBuffers[i];
|
float *restrict input = this->inputAudioBuffers.at(i);
|
||||||
float *restrict finalOutput = this->outputAudioBuffers[i];
|
float *restrict finalOutput = this->outputAudioBuffers.at(i);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < samples; ++i)
|
for (uint32_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
@@ -740,19 +1040,19 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
// replace the contents of the output buffer(s) with the input buffer(s).
|
// replace the contents of the output buffer(s) with the input buffer(s).
|
||||||
if (this->outputAudioBuffers.size() == 1)
|
if (this->outputAudioBuffers.size() == 1)
|
||||||
{
|
{
|
||||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (this->inputAudioBuffers.size() == 1)
|
if (this->inputAudioBuffers.size() == 1)
|
||||||
{
|
{
|
||||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[1], samples);
|
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(1), samples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||||
CopyBuffer(this->inputAudioBuffers[1], this->outputAudioBuffers[1], samples);
|
CopyBuffer(this->inputAudioBuffers.at(1), this->outputAudioBuffers.at(1), samples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // else leave the output alone.
|
} // else leave the output alone.
|
||||||
@@ -765,8 +1065,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
|
|
||||||
if (this->outputAudioBuffers.size() == 1)
|
if (this->outputAudioBuffers.size() == 1)
|
||||||
{
|
{
|
||||||
float * restrict input = this->inputAudioBuffers[0];
|
float *restrict input = this->inputAudioBuffers.at(0);
|
||||||
float * restrict output = this->outputAudioBuffers[0];
|
float *restrict output = this->outputAudioBuffers.at(0);
|
||||||
for (uint32_t i = 0; i < samples; ++i)
|
for (uint32_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i];
|
output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i];
|
||||||
@@ -781,19 +1081,19 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float * restrict inputL;
|
float *restrict inputL;
|
||||||
float * restrict inputR;
|
float *restrict inputR;
|
||||||
if (this->inputAudioBuffers.size() == 1)
|
if (this->inputAudioBuffers.size() == 1)
|
||||||
{
|
{
|
||||||
inputL = inputR = inputAudioBuffers[0];
|
inputL = inputR = inputAudioBuffers.at(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inputL = inputAudioBuffers[0];
|
inputL = inputAudioBuffers.at(0);
|
||||||
inputR = inputAudioBuffers[1];
|
inputR = inputAudioBuffers.at(1);
|
||||||
}
|
}
|
||||||
float * restrict outputL = outputAudioBuffers[0];
|
float *restrict outputL = outputAudioBuffers.at(0);
|
||||||
float * restrict outputR = outputAudioBuffers[1];
|
float *restrict outputR = outputAudioBuffers.at(1);
|
||||||
for (uint32_t i = 0; i < samples; ++i)
|
for (uint32_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i];
|
outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i];
|
||||||
@@ -822,6 +1122,24 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
|||||||
RelayPatchSetMessages(this->instanceId, realtimeRingBufferWriter);
|
RelayPatchSetMessages(this->instanceId, realtimeRingBufferWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||||
|
{
|
||||||
|
// close off the atom input frame.
|
||||||
|
if (this->inputAtomBuffers.size() != 0)
|
||||||
|
{
|
||||||
|
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
|
||||||
|
}
|
||||||
|
lilv_instance_run(pInstance, samples);
|
||||||
|
|
||||||
|
if (worker)
|
||||||
|
{
|
||||||
|
// relay worker response
|
||||||
|
worker->EmitResponses();
|
||||||
|
}
|
||||||
|
|
||||||
|
MixOutput(samples, realtimeRingBufferWriter);
|
||||||
|
}
|
||||||
|
|
||||||
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
|
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
|
||||||
uint32_t size,
|
uint32_t size,
|
||||||
const void *data)
|
const void *data)
|
||||||
@@ -880,17 +1198,17 @@ void Lv2Effect::ResetAtomBuffers()
|
|||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->inputAtomBuffers.size(); ++i)
|
for (size_t i = 0; i < this->inputAtomBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
ResetInputAtomBuffer(this->inputAtomBuffers[i]);
|
ResetInputAtomBuffer(this->inputAtomBuffers.at(i));
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < this->outputAtomBuffers.size(); ++i)
|
for (size_t i = 0; i < this->outputAtomBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
ResetOutputAtomBuffer(this->outputAtomBuffers[i]);
|
ResetOutputAtomBuffer(this->outputAtomBuffers.at(i));
|
||||||
}
|
}
|
||||||
if (inputAtomBuffers.size() != 0)
|
if (inputAtomBuffers.size() != 0)
|
||||||
{
|
{
|
||||||
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||||
lv2_atom_forge_set_buffer(
|
lv2_atom_forge_set_buffer(
|
||||||
&(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers[0]), notify_capacity);
|
&(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers.at(0)), notify_capacity);
|
||||||
|
|
||||||
// Start a sequence in the notify input port.
|
// Start a sequence in the notify input port.
|
||||||
|
|
||||||
@@ -1147,13 +1465,13 @@ uint64_t Lv2Effect::GetMaxInputControl() const { return maxInputControlPort; }
|
|||||||
|
|
||||||
bool Lv2Effect::IsInputControl(uint64_t index) const
|
bool Lv2Effect::IsInputControl(uint64_t index) const
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= isInputControlPort.size())
|
if (index >= isInputControlPort.size())
|
||||||
return false;
|
return false;
|
||||||
return isInputControlPort[index];
|
return isInputControlPort.at(index);
|
||||||
}
|
}
|
||||||
float Lv2Effect::GetDefaultInputControlValue(uint64_t index) const
|
float Lv2Effect::GetDefaultInputControlValue(uint64_t index) const
|
||||||
{
|
{
|
||||||
return defaultInputControlValues[index];
|
return defaultInputControlValues.at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Lv2Effect::GetPathPatchProperty(const std::string &propertyUri)
|
std::string Lv2Effect::GetPathPatchProperty(const std::string &propertyUri)
|
||||||
@@ -1168,3 +1486,97 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
|
|||||||
{
|
{
|
||||||
mainThreadPathProperties[propertyUri] = jsonAtom;
|
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs)
|
||||||
|
{
|
||||||
|
|
||||||
|
stagingBufferSize = bufferSize;
|
||||||
|
stagingOutputIx = bufferSize;
|
||||||
|
stagingInputIx = 0;
|
||||||
|
inputStagingBuffers.resize(nInputs);
|
||||||
|
outputStagingBuffers.resize(nOutputs);
|
||||||
|
inputStagingBufferPointers.resize(nInputs);
|
||||||
|
outputStagingBufferPointers.resize(nOutputs);
|
||||||
|
|
||||||
|
if (inputAtomBuffers.size() != 0)
|
||||||
|
{
|
||||||
|
stagedInputAtomBuffer.resize(pHost->GetAtomBufferSize());
|
||||||
|
stagedInputAtomBufferPointer = stagedInputAtomBuffer.data();
|
||||||
|
resetStagedInputAtomBuffer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stagedInputAtomBufferPointer = nullptr;
|
||||||
|
}
|
||||||
|
stagedOutputAtomBufferPointer = nullptr;
|
||||||
|
if (outputAtomBuffers.size() != 0)
|
||||||
|
{
|
||||||
|
stagedOutputAtomBuffer.resize(pHost->GetAtomBufferSize());
|
||||||
|
stagedOutputAtomBufferPointer = stagedOutputAtomBuffer.data();
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < nInputs; ++i)
|
||||||
|
{
|
||||||
|
inputStagingBuffers.at(i).resize(bufferSize + 1);
|
||||||
|
inputStagingBuffers[i][bufferSize] = 99.9f; // guard entry
|
||||||
|
inputStagingBufferPointers.at(i) = inputStagingBuffers.at(i).data();
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < nOutputs; ++i)
|
||||||
|
{
|
||||||
|
outputStagingBuffers.at(i).resize(bufferSize + 1);
|
||||||
|
outputStagingBuffers[i][bufferSize] = 99.9f; // guard entry
|
||||||
|
outputStagingBufferPointers.at(i) = outputStagingBuffers.at(i).data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t NextPowerOfTwo(size_t value)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t i = 1;
|
||||||
|
while (i < value && i < 65536UL)
|
||||||
|
{
|
||||||
|
i *= 2;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Lv2Effect::GetStagedBufferSize() const
|
||||||
|
{
|
||||||
|
size_t pluginBlockLength = pHost->GetMaxAudioBufferSize();
|
||||||
|
if (info->minBlockLength() != -1 || info->maxBlockLength() != -1)
|
||||||
|
{
|
||||||
|
if (info->minBlockLength() != -1 && pluginBlockLength < info->minBlockLength())
|
||||||
|
{
|
||||||
|
pluginBlockLength = info->minBlockLength();
|
||||||
|
}
|
||||||
|
if (info->maxBlockLength() != -1 && pluginBlockLength > info->maxBlockLength())
|
||||||
|
{
|
||||||
|
pluginBlockLength = info->maxBlockLength();
|
||||||
|
}
|
||||||
|
if (info->powerOf2BlockLength())
|
||||||
|
{
|
||||||
|
pluginBlockLength = NextPowerOfTwo(pluginBlockLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pluginBlockLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lv2Effect::RequiresBufferStaging() const
|
||||||
|
{
|
||||||
|
return GetStagedBufferSize() != pHost->GetMaxAudioBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
float *Lv2Effect::GetAudioInputBuffer(int index) const
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= this->inputAudioBuffers.size())
|
||||||
|
throw std::range_error("Lv2Effect::GetAudioInputBuffer");
|
||||||
|
return this->inputAudioBuffers.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
float *Lv2Effect::GetAudioOutputBuffer(int index) const
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= this->outputAudioBuffers.size())
|
||||||
|
{
|
||||||
|
throw std::range_error("Lv2Effect::GetAudioOutputBuffer");
|
||||||
|
}
|
||||||
|
return this->outputAudioBuffers.at(index);
|
||||||
|
}
|
||||||
|
|||||||
+37
-4
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Robin Davies
|
// Copyright (c) 2025 Robin Davies
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
// 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
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "PatchPropertyWriter.hpp"
|
#include "PatchPropertyWriter.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "MapPathFeature.hpp"
|
#include "MapPathFeature.hpp"
|
||||||
|
#include "OptionsFeature.hpp"
|
||||||
|
|
||||||
#include "IEffect.hpp"
|
#include "IEffect.hpp"
|
||||||
#include "Worker.hpp"
|
#include "Worker.hpp"
|
||||||
@@ -56,7 +57,7 @@ namespace pipedal
|
|||||||
virtual void OnLogDebug(const char*message);
|
virtual void OnLogDebug(const char*message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
size_t GetStagedBufferSize() const;
|
||||||
|
|
||||||
std::shared_ptr<HostWorkerThread> workerThread;
|
std::shared_ptr<HostWorkerThread> workerThread;
|
||||||
std::unique_ptr<Worker> worker;
|
std::unique_ptr<Worker> worker;
|
||||||
@@ -115,6 +116,7 @@ namespace pipedal
|
|||||||
LV2_Atom_Forge_Frame input_frame;
|
LV2_Atom_Forge_Frame input_frame;
|
||||||
|
|
||||||
LV2_Atom_Forge outputForgeRt;
|
LV2_Atom_Forge outputForgeRt;
|
||||||
|
LV2_Atom_Forge_Frame output_frame;
|
||||||
|
|
||||||
std::vector<LV2_URID> pathProperties;
|
std::vector<LV2_URID> pathProperties;
|
||||||
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||||
@@ -210,8 +212,11 @@ namespace pipedal
|
|||||||
|
|
||||||
bool borrowedEffect = false;
|
bool borrowedEffect = false;
|
||||||
bool activated = false;
|
bool activated = false;
|
||||||
|
void EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs);
|
||||||
|
void CheckStagingBufferSentries();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
bool RequiresBufferStaging() const;
|
||||||
bool IsBorrowedEffect() const { return borrowedEffect; }
|
bool IsBorrowedEffect() const { return borrowedEffect; }
|
||||||
void SetBorrowedEffect(bool value) { borrowedEffect = value; }
|
void SetBorrowedEffect(bool value) { borrowedEffect = value; }
|
||||||
void UpdateAudioPorts();
|
void UpdateAudioPorts();
|
||||||
@@ -247,9 +252,36 @@ namespace pipedal
|
|||||||
return (uint8_t*)this->outputAtomBuffers[0];
|
return (uint8_t*)this->outputAtomBuffers[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
OptionsFeature optionsFeature;
|
||||||
|
|
||||||
bool hasErrorMessage = false;
|
bool hasErrorMessage = false;
|
||||||
char errorMessage[1024];
|
char errorMessage[1024];
|
||||||
|
|
||||||
|
bool deleted = false;
|
||||||
|
size_t stagingBufferSize = 0;
|
||||||
|
size_t stagingInputIx = 0;
|
||||||
|
size_t stagingOutputIx = 0;
|
||||||
|
std::vector<std::vector<float>> inputStagingBuffers;
|
||||||
|
std::vector<std::vector<float>> outputStagingBuffers;
|
||||||
|
std::vector<float*> inputStagingBufferPointers;
|
||||||
|
std::vector<float*> outputStagingBufferPointers;
|
||||||
|
|
||||||
|
std::vector<uint8_t> stagedInputAtomBuffer;
|
||||||
|
void *stagedInputAtomBufferPointer = nullptr;
|
||||||
|
std::vector<uint8_t> stagedOutputAtomBuffer;
|
||||||
|
void *stagedOutputAtomBufferPointer = nullptr;
|
||||||
|
|
||||||
|
size_t stageToOutput(size_t outputIndex, size_t nFrames);
|
||||||
|
size_t stageToInput(size_t inputIndex, size_t nFrames);
|
||||||
|
|
||||||
|
|
||||||
|
LV2_Atom_Forge stagedInputForgeRt;
|
||||||
|
LV2_Atom_Forge_Frame staged_input_frame;
|
||||||
|
|
||||||
|
void MixOutput(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||||
|
void copyAtomBufferEventSequence(LV2_Atom_Sequence *sequence, LV2_Atom_Forge &outputForge);
|
||||||
|
void resetStagedInputAtomBuffer();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Lv2Effect(
|
Lv2Effect(
|
||||||
IHost *pHost,
|
IHost *pHost,
|
||||||
@@ -280,14 +312,14 @@ namespace pipedal
|
|||||||
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); }
|
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); }
|
||||||
|
|
||||||
virtual void SetAudioInputBuffer(int index, float *buffer);
|
virtual void SetAudioInputBuffer(int index, float *buffer);
|
||||||
virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
|
virtual float *GetAudioInputBuffer(int index) const;
|
||||||
|
|
||||||
|
|
||||||
virtual void SetAudioInputBuffer(float *buffer);
|
virtual void SetAudioInputBuffer(float *buffer);
|
||||||
virtual void SetAudioInputBuffers(float *left, float *right);
|
virtual void SetAudioInputBuffers(float *left, float *right);
|
||||||
|
|
||||||
virtual void SetAudioOutputBuffer(int index, float *buffer);
|
virtual void SetAudioOutputBuffer(int index, float *buffer);
|
||||||
virtual float *GetAudioOutputBuffer(int index) const { return this->outputAudioBuffers[index]; }
|
virtual float *GetAudioOutputBuffer(int index) const;
|
||||||
|
|
||||||
virtual void SetAtomInputBuffer(int index, void *buffer) { this->inputAtomBuffers[index] = (char*)buffer;}
|
virtual void SetAtomInputBuffer(int index, void *buffer) { this->inputAtomBuffers[index] = (char*)buffer;}
|
||||||
virtual void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
|
virtual void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
|
||||||
@@ -345,6 +377,7 @@ namespace pipedal
|
|||||||
|
|
||||||
virtual void Activate();
|
virtual void Activate();
|
||||||
virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||||
|
virtual void RunWithBufferStaging(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||||
virtual void Deactivate();
|
virtual void Deactivate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+42
-19
@@ -59,6 +59,7 @@ int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbo
|
|||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<float *> Lv2Pedalboard::PrepareItems(
|
std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||||
std::vector<PedalboardItem> &items,
|
std::vector<PedalboardItem> &items,
|
||||||
std::vector<float *> inputBuffers,
|
std::vector<float *> inputBuffers,
|
||||||
@@ -88,8 +89,8 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
|
|
||||||
this->processActions.push_back(preMixAction);
|
this->processActions.push_back(preMixAction);
|
||||||
|
|
||||||
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs, errorList,existingEffects);
|
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs, errorList, existingEffects);
|
||||||
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList,existingEffects);
|
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList, existingEffects);
|
||||||
|
|
||||||
this->processActions.push_back(
|
this->processActions.push_back(
|
||||||
[pSplit](uint32_t frames)
|
[pSplit](uint32_t frames)
|
||||||
@@ -114,11 +115,10 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
{
|
{
|
||||||
std::shared_ptr<IEffect> pLv2Effect;
|
std::shared_ptr<IEffect> pLv2Effect;
|
||||||
|
|
||||||
if (existingEffects && existingEffects->contains(item.instanceId())
|
if (existingEffects && existingEffects->contains(item.instanceId()))
|
||||||
)
|
|
||||||
{
|
{
|
||||||
pLv2Effect = existingEffects->at(item.instanceId());
|
pLv2Effect = existingEffects->at(item.instanceId());
|
||||||
((Lv2Effect*)pLv2Effect.get())->SetBorrowedEffect(true);
|
((Lv2Effect *)pLv2Effect.get())->SetBorrowedEffect(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -177,16 +177,38 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->processActions.push_back(
|
// check to see whether we need buffer staging.
|
||||||
[pLv2Effect, this](uint32_t frames)
|
bool requiresBufferStaging = false;
|
||||||
{
|
|
||||||
pLv2Effect->Run(frames, this->ringBufferWriter);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset any trigger controls to default state after processing
|
|
||||||
if (pLv2Effect->IsLv2Effect())
|
if (pLv2Effect->IsLv2Effect())
|
||||||
{
|
{
|
||||||
Lv2Effect *lv2Effect = (Lv2Effect *)pLv2Effect.get();
|
Lv2Effect *lv2Effect = (Lv2Effect *)pLv2Effect.get();
|
||||||
|
|
||||||
|
if (lv2Effect->RequiresBufferStaging())
|
||||||
|
{
|
||||||
|
requiresBufferStaging = true;
|
||||||
|
this->processActions.push_back(
|
||||||
|
[lv2Effect, this](uint32_t frames)
|
||||||
|
{
|
||||||
|
lv2Effect->RunWithBufferStaging(frames, this->ringBufferWriter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!requiresBufferStaging)
|
||||||
|
{
|
||||||
|
this->processActions.push_back(
|
||||||
|
[pLv2Effect, this](uint32_t frames)
|
||||||
|
{
|
||||||
|
pLv2Effect->Run(frames, this->ringBufferWriter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset any trigger controls to default state after processing
|
||||||
|
if (pLv2Effect->IsLv2Effect())
|
||||||
|
{
|
||||||
|
Lv2Effect *lv2Effect = (Lv2Effect *)pLv2Effect.get();
|
||||||
|
|
||||||
auto pluginInfo = pHost->GetPluginInfo(item.uri());
|
auto pluginInfo = pHost->GetPluginInfo(item.uri());
|
||||||
if (pluginInfo)
|
if (pluginInfo)
|
||||||
{
|
{
|
||||||
@@ -212,7 +234,8 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
|||||||
}
|
}
|
||||||
if (pEffect)
|
if (pEffect)
|
||||||
{
|
{
|
||||||
this->effects.push_back(pEffect); // for ownership.
|
this->effects.push_back(pEffect); // for ownership.
|
||||||
|
|
||||||
this->realtimeEffects.push_back(pEffect.get()); // because std::shared_ptr is not threadsafe.
|
this->realtimeEffects.push_back(pEffect.get()); // because std::shared_ptr is not threadsafe.
|
||||||
|
|
||||||
std::vector<float *> effectOutput;
|
std::vector<float *> effectOutput;
|
||||||
@@ -326,7 +349,6 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
|
|||||||
mapping.midiBinding = binding;
|
mapping.midiBinding = binding;
|
||||||
mapping.instanceId = pedalboardItem.instanceId();
|
mapping.instanceId = pedalboardItem.instanceId();
|
||||||
|
|
||||||
|
|
||||||
if (pPortInfo->mod_momentaryOffByDefault() || pPortInfo->mod_momentaryOnByDefault())
|
if (pPortInfo->mod_momentaryOffByDefault() || pPortInfo->mod_momentaryOnByDefault())
|
||||||
{
|
{
|
||||||
mapping.mappingType = MidiControlType::MomentarySwitch;
|
mapping.mappingType = MidiControlType::MomentarySwitch;
|
||||||
@@ -397,14 +419,14 @@ void Lv2Pedalboard::UpdateAudioPorts()
|
|||||||
{
|
{
|
||||||
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
|
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
|
||||||
lv2Effect->UpdateAudioPorts();
|
lv2Effect->UpdateAudioPorts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lv2Pedalboard::Activate()
|
void Lv2Pedalboard::Activate()
|
||||||
{
|
{
|
||||||
CrashGuardLock crashGuardLock;
|
CrashGuardLock crashGuardLock;
|
||||||
|
|
||||||
for (int i = 0; i < this->effects.size(); ++i)
|
for (int i = 0; i < this->effects.size(); ++i)
|
||||||
{
|
{
|
||||||
this->realtimeEffects[i]->Activate();
|
this->realtimeEffects[i]->Activate();
|
||||||
@@ -435,7 +457,7 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < samples; ++i)
|
for (size_t i = 0; i < samples; ++i)
|
||||||
{
|
{
|
||||||
float volume = this->inputVolume.Tick();
|
float volume = this->inputVolume.Tick();
|
||||||
@@ -581,12 +603,13 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
|||||||
else if (pEffect->IsVst3())
|
else if (pEffect->IsVst3())
|
||||||
{
|
{
|
||||||
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
||||||
} else if (pParameterRequests->sampleTimeout < 0)
|
}
|
||||||
|
else if (pParameterRequests->sampleTimeout < 0)
|
||||||
{
|
{
|
||||||
pParameterRequests->sampleTimeout = 0;
|
pParameterRequests->sampleTimeout = 0;
|
||||||
pParameterRequests->errorMessage = "Timed out.";
|
pParameterRequests->errorMessage = "Timed out.";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (pEffect->IsLv2Effect())
|
if (pEffect->IsLv2Effect())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace pipedal {
|
|||||||
double sampleRate,
|
double sampleRate,
|
||||||
int32_t blockLength,
|
int32_t blockLength,
|
||||||
int32_t atomBufferBlockLength);
|
int32_t atomBufferBlockLength);
|
||||||
|
|
||||||
|
void SetBlockLength(int32_t blockLength) { this->blockLength = blockLength; }
|
||||||
|
|
||||||
~OptionsFeature();
|
~OptionsFeature();
|
||||||
public:
|
public:
|
||||||
const LV2_Feature* GetFeature()
|
const LV2_Feature* GetFeature()
|
||||||
|
|||||||
+231
-118
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Robin Davies
|
// Copyright (c) Robin E. R. Davies
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
// 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
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -52,9 +52,106 @@ static bool isSupportedAudioDevice(const AlsaDeviceInfo &d)
|
|||||||
std::string name = d.name_ + " " + d.longName_;
|
std::string name = d.name_ + " " + d.longName_;
|
||||||
std::transform(name.begin(), name.end(), name.begin(), [](char c)
|
std::transform(name.begin(), name.end(), name.begin(), [](char c)
|
||||||
{ return std::tolower(c); });
|
{ return std::tolower(c); });
|
||||||
return name.find("hdmi") != std::string::npos || name.find("bcm2835") != std::string::npos;
|
if (name.find("hdmi") != std::string::npos) return false;
|
||||||
|
// if (name.find("bcm2835") != std::string::npos) return false;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ProcAlsaDevice {
|
||||||
|
int cardId;
|
||||||
|
int subdeviceId;
|
||||||
|
bool audioCapture;
|
||||||
|
bool audioPlayback;
|
||||||
|
bool rawMidi;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<ProcAlsaDevice> getProcAlsaDevices()
|
||||||
|
{
|
||||||
|
std::vector<ProcAlsaDevice> result;
|
||||||
|
|
||||||
|
std::ifstream f {"/proc/asound/devices"};
|
||||||
|
if (f.is_open())
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(f, line))
|
||||||
|
{
|
||||||
|
// Parse each line of /proc/alsa/devices
|
||||||
|
// Format: cardnum: [devicenum- subdevicenum]: type : name
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string token;
|
||||||
|
|
||||||
|
// Skip leading whitespace and get card number
|
||||||
|
if (!std::getline(iss, token, ':'))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int cardId = std::stoi(token.substr(token.find_first_not_of(" \t")));
|
||||||
|
|
||||||
|
// Get device-subdevice part
|
||||||
|
if (!std::getline(iss, token, ':'))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t dashPos = token.find('-');
|
||||||
|
if (dashPos == std::string::npos)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int deviceId = std::stoi(token.substr(token.find_first_not_of(" \t["), dashPos));
|
||||||
|
int subdeviceId = std::stoi(token.substr(dashPos + 1, token.find(']') - dashPos - 1));
|
||||||
|
|
||||||
|
// Get type
|
||||||
|
if (!std::getline(iss, token, ':'))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string type = token.substr(token.find_first_not_of(" \t"));
|
||||||
|
|
||||||
|
ProcAlsaDevice *pDevice = nullptr;
|
||||||
|
for (size_t i = 0; i < result.size(); ++i)
|
||||||
|
{
|
||||||
|
if (result[i].cardId == deviceId && result[i].subdeviceId == subdeviceId)
|
||||||
|
{
|
||||||
|
pDevice = &result[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pDevice == nullptr)
|
||||||
|
{
|
||||||
|
ProcAlsaDevice newDevice;
|
||||||
|
newDevice.cardId = deviceId;
|
||||||
|
newDevice.subdeviceId = subdeviceId;
|
||||||
|
newDevice.audioCapture = false;
|
||||||
|
newDevice.audioPlayback = false;
|
||||||
|
newDevice.rawMidi = false;
|
||||||
|
result.push_back(newDevice);
|
||||||
|
pDevice = &(result[result.size()-1]);
|
||||||
|
}
|
||||||
|
if (type.find("digital audio capture") != std::string::npos)
|
||||||
|
{
|
||||||
|
pDevice->audioCapture = true;
|
||||||
|
}
|
||||||
|
if ((type.find("digital audio playback") != std::string::npos))
|
||||||
|
{
|
||||||
|
pDevice->audioPlayback = true;
|
||||||
|
}
|
||||||
|
if (type.find("rawmidi") != std::string::npos)
|
||||||
|
{
|
||||||
|
pDevice->rawMidi = true;
|
||||||
|
}
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::error(SS("invalid ALSA proc entry: " << line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -65,152 +162,163 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
int cardNum = -1; // Start with first card
|
int cardNum = -1; // Start with first card
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
for (;;)
|
std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices();
|
||||||
|
|
||||||
|
for (const auto &procAlsaDevice: procAlsaDevices)
|
||||||
{
|
{
|
||||||
if ((err = snd_card_next(&cardNum)) < 0)
|
std::stringstream ss;
|
||||||
{
|
if (!procAlsaDevice.audioCapture && !procAlsaDevice.audioPlayback) {
|
||||||
Lv2Log::error("Unexpected error enumerating ALSA devices.");
|
continue;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (cardNum < 0)
|
ss << "hw:" << procAlsaDevice.cardId;
|
||||||
// No more cards
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
std::string cardId = ss.str();
|
||||||
|
|
||||||
|
snd_ctl_t *hDevice = nullptr;
|
||||||
|
|
||||||
|
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
continue;
|
||||||
ss << "hw:" << cardNum;
|
}
|
||||||
std::string cardId = ss.str();
|
|
||||||
|
|
||||||
snd_ctl_t *hDevice = nullptr;
|
Finally ffhDevice{[hDevice]()
|
||||||
|
{ snd_ctl_close(hDevice); }};
|
||||||
|
|
||||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
snd_ctl_card_info_t *alsaInfo = nullptr;
|
||||||
|
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Failed to allocate ALSA card info");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Finally ffCardInfo{[alsaInfo]()
|
||||||
|
{ snd_ctl_card_info_free(alsaInfo); }};
|
||||||
|
|
||||||
|
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
|
AlsaDeviceInfo info;
|
||||||
|
info.cardId_ = cardNum;
|
||||||
|
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||||
|
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||||
|
(void)driver;
|
||||||
|
|
||||||
|
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||||
|
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||||
|
|
||||||
|
// we can't read our own device if it's open so use data that gets
|
||||||
|
// cached before we open audio devices.
|
||||||
|
|
||||||
|
AlsaDeviceInfo cachedInfo;
|
||||||
|
if (getCachedDevice(info.name_, &cachedInfo))
|
||||||
{
|
{
|
||||||
|
// may have been plugged into a different USB connector.
|
||||||
|
cachedInfo.cardId_ = info.cardId_;
|
||||||
|
cachedInfo.id_ = info.id_;
|
||||||
|
result.push_back(cachedInfo);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
snd_pcm_t *captureDevice = nullptr;
|
||||||
|
snd_pcm_t *playbackDevice = nullptr;
|
||||||
|
auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||||
|
bool captureOk = rc == 0;
|
||||||
|
|
||||||
Finally ffhDevice{[hDevice]()
|
Finally ffCaptureDevice{
|
||||||
{ snd_ctl_close(hDevice); }};
|
[captureDevice]
|
||||||
|
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
||||||
|
|
||||||
snd_ctl_card_info_t *alsaInfo = nullptr;
|
rc = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
bool playbackOk = rc == 0;
|
||||||
|
|
||||||
|
Finally ffPlaybackDevice{
|
||||||
|
[playbackDevice]
|
||||||
|
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
||||||
|
if (procAlsaDevice.audioCapture && !captureOk)
|
||||||
{
|
{
|
||||||
Lv2Log::error("Failed to allocate ALSA card info");
|
info.captureBusy_ = true;
|
||||||
continue;
|
}
|
||||||
|
if (procAlsaDevice.audioPlayback && !playbackOk)
|
||||||
|
{
|
||||||
|
info.playbackBusy_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Finally ffCardInfo{[alsaInfo]()
|
info.supportsCapture_ = captureOk;
|
||||||
{ snd_ctl_card_info_free(alsaInfo); }};
|
info.supportsPlayback_ = playbackOk;
|
||||||
|
|
||||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
if (captureOk || playbackOk)
|
||||||
if (err == 0)
|
|
||||||
{
|
{
|
||||||
AlsaDeviceInfo info;
|
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
||||||
info.cardId_ = cardNum;
|
snd_pcm_hw_params_t *params = nullptr;
|
||||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
err = snd_pcm_hw_params_malloc(¶ms);
|
||||||
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
|
||||||
(void)driver;
|
|
||||||
|
|
||||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
if (err == 0)
|
||||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
|
||||||
|
|
||||||
// we can't read our own device if it's open so use data that gets
|
|
||||||
// cached before we open audio devices.
|
|
||||||
|
|
||||||
AlsaDeviceInfo cachedInfo;
|
|
||||||
if (getCachedDevice(info.name_, &cachedInfo))
|
|
||||||
{
|
{
|
||||||
// may have been plugged into a different USB connector.
|
Finally ffParams{[params]
|
||||||
cachedInfo.cardId_ = info.cardId_;
|
{ snd_pcm_hw_params_free(params); }};
|
||||||
cachedInfo.id_ = info.id_;
|
|
||||||
result.push_back(cachedInfo);
|
|
||||||
|
|
||||||
}
|
err = snd_pcm_hw_params_any(hDevice, params);
|
||||||
else
|
if (err == 0)
|
||||||
{
|
|
||||||
snd_pcm_t *captureDevice = nullptr;
|
|
||||||
snd_pcm_t *playbackDevice = nullptr;
|
|
||||||
bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) == 0;
|
|
||||||
bool playbackOk = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) == 0;
|
|
||||||
Finally ffCaptureDevice{
|
|
||||||
[captureDevice]
|
|
||||||
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
|
||||||
Finally ffPlaybackDevice{
|
|
||||||
[playbackDevice]
|
|
||||||
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
|
||||||
|
|
||||||
info.supportsCapture_ = captureOk;
|
|
||||||
info.supportsPlayback_ = playbackOk;
|
|
||||||
|
|
||||||
if (captureOk || playbackOk)
|
|
||||||
{
|
{
|
||||||
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
unsigned int minRate = 0, maxRate = 0;
|
||||||
snd_pcm_hw_params_t *params = nullptr;
|
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||||
err = snd_pcm_hw_params_malloc(¶ms);
|
int dir;
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
|
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||||
|
{
|
||||||
|
uint32_t rate = RATES[i];
|
||||||
|
if (rate >= minRate && rate <= maxRate)
|
||||||
|
{
|
||||||
|
info.sampleRates_.push_back(rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
||||||
|
}
|
||||||
|
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
Finally ffParams{[params]
|
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||||
{ snd_pcm_hw_params_free(params); }};
|
|
||||||
|
|
||||||
err = snd_pcm_hw_params_any(hDevice, params);
|
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
unsigned int minRate = 0, maxRate = 0;
|
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
|
||||||
int dir;
|
|
||||||
|
|
||||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
|
||||||
{
|
|
||||||
uint32_t rate = RATES[i];
|
|
||||||
if (rate >= minRate && rate <= maxRate)
|
|
||||||
{
|
|
||||||
info.sampleRates_.push_back(rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
if (minBufferSize < 16)
|
|
||||||
{
|
|
||||||
minBufferSize = 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
|
||||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
cacheDevice(info.name_, info);
|
if (minBufferSize < 16)
|
||||||
result.push_back(info);
|
{
|
||||||
|
minBufferSize = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||||
|
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!info.captureBusy_ && !info.playbackBusy_)
|
||||||
|
{
|
||||||
|
cacheDevice(info.name_, info);
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (info.captureBusy_ || info.playbackBusy_)
|
||||||
|
{
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,14 +328,17 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
std::vector<AlsaDeviceInfo> filtered;
|
std::vector<AlsaDeviceInfo> filtered;
|
||||||
for (auto &device : result)
|
for (auto &device : result)
|
||||||
{
|
{
|
||||||
if (!isSupportedAudioDevice(device))
|
if (isSupportedAudioDevice(device))
|
||||||
{
|
{
|
||||||
filtered.push_back(device);
|
filtered.push_back(device);
|
||||||
Lv2Log::debug(
|
Lv2Log::debug(
|
||||||
SS(" "
|
SS(" "
|
||||||
<< device.name_ << " " << device.longName_ << " " << device.cardId_
|
<< device.name_ << " " << device.longName_ << " " << device.cardId_
|
||||||
<< (device.supportsCapture_ ? " in" : "")
|
<< (device.supportsCapture_ ? " in" : "")
|
||||||
<< (device.supportsPlayback_ ? " out" : "")));
|
<< (device.supportsPlayback_ ? " out" : "")
|
||||||
|
<< (device.captureBusy_ ? " in(busy)" : "")
|
||||||
|
<< (device.captureBusy_ ? " out(busy)" : "")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered;
|
return filtered;
|
||||||
@@ -535,6 +646,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
|||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
|
||||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
|
||||||
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, captureBusy)
|
||||||
|
JSON_MAP_REFERENCE(AlsaDeviceInfo, playbackBusy)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ namespace pipedal {
|
|||||||
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
||||||
bool supportsCapture_ = false;
|
bool supportsCapture_ = false;
|
||||||
bool supportsPlayback_ = false;
|
bool supportsPlayback_ = false;
|
||||||
|
bool captureBusy_ = false;
|
||||||
|
bool playbackBusy_ = false;
|
||||||
|
|
||||||
bool isDummyDevice() const {
|
bool isDummyDevice() const {
|
||||||
return id_.starts_with("dummy:");
|
return id_.starts_with("dummy:");
|
||||||
|
|||||||
+12
-5
@@ -30,7 +30,6 @@
|
|||||||
#include "Pedalboard.hpp"
|
#include "Pedalboard.hpp"
|
||||||
#include "Lv2Effect.hpp"
|
#include "Lv2Effect.hpp"
|
||||||
#include "Lv2Pedalboard.hpp"
|
#include "Lv2Pedalboard.hpp"
|
||||||
#include "OptionsFeature.hpp"
|
|
||||||
#include "JackConfiguration.hpp"
|
#include "JackConfiguration.hpp"
|
||||||
#include "lv2/urid/urid.h"
|
#include "lv2/urid/urid.h"
|
||||||
#include "lv2/ui/ui.h"
|
#include "lv2/ui/ui.h"
|
||||||
@@ -303,8 +302,6 @@ PluginHost::PluginHost()
|
|||||||
lv2Features.push_back(mapFeature.GetMapFeature());
|
lv2Features.push_back(mapFeature.GetMapFeature());
|
||||||
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
||||||
|
|
||||||
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
|
|
||||||
lv2Features.push_back(optionsFeature.GetFeature());
|
|
||||||
|
|
||||||
fileMetadataFeature.Prepare(mapFeature);
|
fileMetadataFeature.Prepare(mapFeature);
|
||||||
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
||||||
@@ -322,7 +319,6 @@ void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration,
|
|||||||
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
||||||
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
|
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
|
||||||
this->maxBufferSize = configuration.blockLength();
|
this->maxBufferSize = configuration.blockLength();
|
||||||
optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,6 +937,11 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
|||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AutoLilvNode minBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__minBlockLength,nullptr);
|
||||||
|
this->minBlockLength_ = minBlockLength.AsFloat(-1);
|
||||||
|
AutoLilvNode maxBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__maxBlockLength,nullptr);
|
||||||
|
this->maxBlockLength_ = maxBlockLength.AsFloat(-1);
|
||||||
|
|
||||||
|
|
||||||
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
||||||
// Fetch patch properties.
|
// Fetch patch properties.
|
||||||
@@ -2217,7 +2218,11 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
|||||||
|
|
||||||
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
|
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
|
||||||
MAP_REF(Lv2PortInfo, custom_units),
|
MAP_REF(Lv2PortInfo, custom_units),
|
||||||
MAP_REF(Lv2PortInfo, comment)}};
|
MAP_REF(Lv2PortInfo, comment),
|
||||||
|
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||||
MAP_REF(Lv2PortGroup, uri),
|
MAP_REF(Lv2PortGroup, uri),
|
||||||
@@ -2249,6 +2254,8 @@ json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
|||||||
json_map::reference("modGui", &Lv2PluginInfo::modGui_),
|
json_map::reference("modGui", &Lv2PluginInfo::modGui_),
|
||||||
json_map::reference("patchProperties", &Lv2PluginInfo::patchProperties_),
|
json_map::reference("patchProperties", &Lv2PluginInfo::patchProperties_),
|
||||||
json_map::reference("hasDefaultState", &Lv2PluginInfo::hasDefaultState_),
|
json_map::reference("hasDefaultState", &Lv2PluginInfo::hasDefaultState_),
|
||||||
|
json_map::reference("minBlockLength", &Lv2PluginInfo::minBlockLength_),
|
||||||
|
json_map::reference("maxBlockLength", &Lv2PluginInfo::maxBlockLength_),
|
||||||
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -26,7 +26,6 @@
|
|||||||
#include <lilv/lilv.h>
|
#include <lilv/lilv.h>
|
||||||
#include "MapFeature.hpp"
|
#include "MapFeature.hpp"
|
||||||
#include "FileMetadataFeature.hpp"
|
#include "FileMetadataFeature.hpp"
|
||||||
#include "OptionsFeature.hpp"
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -432,6 +431,10 @@ namespace pipedal
|
|||||||
|
|
||||||
bool IsSupportedFeature(const std::string &feature) const;
|
bool IsSupportedFeature(const std::string &feature) const;
|
||||||
|
|
||||||
|
bool powerOf2BlockLength_ = false;
|
||||||
|
float minBlockLength_ = -1;
|
||||||
|
float maxBlockLength_ = -1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LV2_PROPERTY_GETSET(bundle_path)
|
LV2_PROPERTY_GETSET(bundle_path)
|
||||||
LV2_PROPERTY_GETSET(uri)
|
LV2_PROPERTY_GETSET(uri)
|
||||||
@@ -457,6 +460,9 @@ namespace pipedal
|
|||||||
LV2_PROPERTY_GETSET(modGui)
|
LV2_PROPERTY_GETSET(modGui)
|
||||||
LV2_PROPERTY_GETSET(patchProperties)
|
LV2_PROPERTY_GETSET(patchProperties)
|
||||||
LV2_PROPERTY_GETSET(hasDefaultState)
|
LV2_PROPERTY_GETSET(hasDefaultState)
|
||||||
|
LV2_PROPERTY_GETSET(minBlockLength)
|
||||||
|
LV2_PROPERTY_GETSET(maxBlockLength)
|
||||||
|
LV2_PROPERTY_GETSET(powerOf2BlockLength)
|
||||||
|
|
||||||
bool WantsWorkerThread() const;
|
bool WantsWorkerThread() const;
|
||||||
|
|
||||||
@@ -877,7 +883,6 @@ namespace pipedal
|
|||||||
std::vector<const LV2_Feature *> lv2Features;
|
std::vector<const LV2_Feature *> lv2Features;
|
||||||
MapFeature mapFeature;
|
MapFeature mapFeature;
|
||||||
FileMetadataFeature fileMetadataFeature;
|
FileMetadataFeature fileMetadataFeature;
|
||||||
OptionsFeature optionsFeature;
|
|
||||||
std::string pluginStoragePath;
|
std::string pluginStoragePath;
|
||||||
|
|
||||||
static void fn_LilvSetPortValueFunc(const char *port_symbol,
|
static void fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export default class AlsaDeviceInfo {
|
|||||||
this.maxBufferSize = input.maxBufferSize;
|
this.maxBufferSize = input.maxBufferSize;
|
||||||
this.supportsCapture = input.supportsCapture ? true : false;
|
this.supportsCapture = input.supportsCapture ? true : false;
|
||||||
this.supportsPlayback = input.supportsPlayback ? true : false;
|
this.supportsPlayback = input.supportsPlayback ? true : false;
|
||||||
|
this.captureBusy = input.captureBusy;
|
||||||
|
this.playbackBusy = input.playbackBusy;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
static deserialize_array(input: any): AlsaDeviceInfo[]
|
static deserialize_array(input: any): AlsaDeviceInfo[]
|
||||||
@@ -67,4 +69,6 @@ export default class AlsaDeviceInfo {
|
|||||||
maxBufferSize: number = 0;
|
maxBufferSize: number = 0;
|
||||||
supportsCapture: boolean = true;
|
supportsCapture: boolean = true;
|
||||||
supportsPlayback: boolean = true;
|
supportsPlayback: boolean = true;
|
||||||
|
captureBusy: boolean = false;
|
||||||
|
playbackBusy: boolean = false;
|
||||||
};
|
};
|
||||||
@@ -81,12 +81,14 @@ export default class JackServerSettings {
|
|||||||
? this.alsaOutputDevice.substring(3)
|
? this.alsaOutputDevice.substring(3)
|
||||||
: this.alsaOutputDevice;
|
: this.alsaOutputDevice;
|
||||||
|
|
||||||
|
let device: string;
|
||||||
if (inDev === outDev) {
|
if (inDev === outDev) {
|
||||||
return inDev;
|
device = inDev;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return inDev + "-> " + outDev;
|
device = inDev + "-> " + outDev;
|
||||||
}
|
}
|
||||||
|
return `${device} ${this.sampleRate} ${this.bufferSize}x${this.numberOfBuffers}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,9 @@ import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
|||||||
function filterDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
|
function filterDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
|
||||||
return devices.filter(d => {
|
return devices.filter(d => {
|
||||||
const name = (d.name + ' ' + d.longName).toLowerCase();
|
const name = (d.name + ' ' + d.longName).toLowerCase();
|
||||||
return !(name.includes('hdmi') || name.includes('bcm2835'));
|
return !(name.includes('hdmi')
|
||||||
|
|| name.includes('bcm2835') // Pi 4 headphones out.
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +347,7 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// Error requesting ALSA info.
|
console.log("Failed to get ALSA device info: " + error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,16 +623,16 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
const devicesSelected = (selectedInputDevice && selectedOutputDevice);
|
const devicesSelected = (selectedInputDevice && selectedOutputDevice);
|
||||||
|
|
||||||
let bufferSizes: number[] = devicesSelected ?
|
let bufferSizes: number[] = devicesSelected ?
|
||||||
getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice) : [];
|
getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice) : [this.state.jackServerSettings.bufferSize];
|
||||||
let bufferCounts = devicesSelected ?
|
let bufferCounts = devicesSelected ?
|
||||||
getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice) : [];
|
getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice) : [this.state.jackServerSettings.numberOfBuffers ];
|
||||||
let bufferSizeDisabled = !devicesSelected;
|
let bufferSizeDisabled = !devicesSelected;
|
||||||
let bufferCountDisabled = !devicesSelected;
|
let bufferCountDisabled = !devicesSelected;
|
||||||
let sampleRates = devicesSelected && selectedInputDevice && selectedOutputDevice ?
|
let sampleRates = devicesSelected && selectedInputDevice && selectedOutputDevice ?
|
||||||
intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates)
|
intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates)
|
||||||
: [];
|
: [];
|
||||||
let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates;
|
let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates;
|
||||||
let ultraCompact = this.windowSize.width < 500;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogEx tag="jack" onClose={() => { this.handleClose(); }} aria-labelledby="select-channels-title" open={this.props.open}
|
<DialogEx tag="jack" onClose={() => { this.handleClose(); }} aria-labelledby="select-channels-title" open={this.props.open}
|
||||||
@@ -654,9 +656,14 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
||||||
style={{ width: 220 }}
|
style={{ width: 220 }}
|
||||||
>
|
>
|
||||||
{sortedDevices.filter(d => d.supportsCapture).map(d => (
|
{sortedDevices.filter(d =>
|
||||||
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
(d.supportsCapture || d.captureBusy)
|
||||||
)) || <MenuItem value="" disabled>Loading...</MenuItem>}
|
).map(d => (
|
||||||
|
<MenuItem key={d.id} disabled={d.captureBusy}
|
||||||
|
value={d.id}
|
||||||
|
style={{ opacity: d.captureBusy ? 0.3 : 1 }}
|
||||||
|
>{d.name}</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
@@ -670,9 +677,15 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
||||||
style={{ width: 220 }}
|
style={{ width: 220 }}
|
||||||
>
|
>
|
||||||
{sortedDevices.filter(d => d.supportsPlayback).map(d => (
|
{sortedDevices.filter(
|
||||||
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
d => (d.supportsPlayback
|
||||||
)) || <MenuItem value="" disabled>Loading...</MenuItem>}
|
|| d.playbackBusy)
|
||||||
|
).map(d => (
|
||||||
|
<MenuItem key={d.id} value={d.id}
|
||||||
|
style={{ opacity: d.playbackBusy ? 0.3 : 1.0 }}
|
||||||
|
disabled={d.playbackBusy}
|
||||||
|
>{d.name}</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import OkCancelDialog from './OkCancelDialog';
|
|||||||
import RadioSelectDialog from './RadioSelectDialog';
|
import RadioSelectDialog from './RadioSelectDialog';
|
||||||
import IconButtonEx from './IconButtonEx';
|
import IconButtonEx from './IconButtonEx';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import {isDarkMode} from './DarkMode';
|
||||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||||
import { ColorTheme } from './DarkMode';
|
import { ColorTheme } from './DarkMode';
|
||||||
import ButtonBase from "@mui/material/ButtonBase";
|
import ButtonBase from "@mui/material/ButtonBase";
|
||||||
@@ -661,7 +662,8 @@ const SettingsDialog = withStyles(
|
|||||||
{(!isConfigValid) ?
|
{(!isConfigValid) ?
|
||||||
(
|
(
|
||||||
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||||
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
|
<Typography display="block" variant="caption" color="textSecondary">Status:
|
||||||
|
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
||||||
{(!this.props.onboarding) && (
|
{(!this.props.onboarding) && (
|
||||||
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user