Alsa device enumeration, Audio Device dialog tuning, minimum buffer size support (gx plugins).

This commit is contained in:
Robin E. R. Davies
2025-09-11 08:59:49 -04:00
parent 584c159c42
commit dd8a500891
16 changed files with 880 additions and 257 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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);
} }
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
{ {
+3
View File
@@ -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
View File
@@ -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(&params);
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(&params); 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)
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+4
View File
@@ -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;
}; };
+4 -2
View File
@@ -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}`;
} }
} }
+24 -11
View File
@@ -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>
+3 -1
View File
@@ -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>
)} )}