// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" #include "Lv2Effect.hpp" #include "PiPedalException.hpp" #include #include #include "lv2/atom.lv2/atom.h" #include "lv2/atom.lv2/util.h" #include "lv2.h" #include "lv2/log.lv2/log.h" #include "lv2/log.lv2/logger.h" #include "lv2/midi.lv2/midi.h" #include "lv2/urid.lv2/urid.h" #include "lv2/log.lv2/logger.h" #include "lv2/uri-map.lv2/uri-map.h" #include "lv2/atom.lv2/forge.h" #include "lv2/worker.lv2/worker.h" #include "lv2/patch.lv2/patch.h" #include "lv2/parameters.lv2/parameters.h" #include "lv2/units.lv2/units.h" #include "lv2/atom.lv2/util.h" #include "JackHost.hpp" #include #include "RingBufferReader.hpp" using namespace pipedal; const float BYPASS_TIME_S = 0.1f; Lv2Effect::Lv2Effect( IHost *pHost_, const std::shared_ptr &info_, const PedalBoardItem &pedalBoardItem) : pHost(pHost_), pInstance(nullptr), info(info_), uris(pHost) { auto pWorld = pHost_->getWorld(); this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S); this->bypass = pedalBoardItem.isEnabled(); // initialize the atom forge used on the realtime thread. LV2_URID_Map *map = this->pHost->GetLv2UridMap(); lv2_atom_forge_init(&inputForgeRt, map); lv2_atom_forge_init(&outputForgeRt, map); const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld); auto uriNode = lilv_new_uri(pWorld, pedalBoardItem.uri().c_str()); const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode); lilv_node_free(uriNode); LV2_Feature *const *features = pHost_->GetLv2Features(); for (auto p = features; *p != nullptr; ++p) { this->features.push_back(*p); } this->work_schedule_feature = nullptr; if (info_->hasExtension(LV2_WORKER__interface)) { // insane implementation. :-( LV2_Worker_Schedule *schedule = (LV2_Worker_Schedule *)malloc(sizeof(LV2_Worker_Schedule)); schedule->handle = this; schedule->schedule_work = worker_schedule_fn; work_schedule_feature = (LV2_Feature *)malloc(sizeof(LV2_Feature)); work_schedule_feature->URI = LV2_WORKER__schedule; work_schedule_feature->data = schedule; this->features.push_back(work_schedule_feature); } this->features.push_back(nullptr); LV2_Feature **myFeatures = &this->features[0]; LilvInstance *pInstance = lilv_plugin_instantiate(pPlugin, pHost->GetSampleRate(), myFeatures); this->pInstance = pInstance; if (this->pInstance == nullptr) { throw PiPedalException("Failed to create plugin."); } if (info_->hasExtension(LV2_WORKER__interface)) { const LV2_Worker_Interface *worker_interface = (const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance, LV2_WORKER__interface); this->worker = std::make_unique(pInstance, worker_interface); } this->instanceId = pedalBoardItem.instanceId(); this->controlValues.resize(info->ports().size()); // Copy default pedalboard settings. for (auto i = pedalBoardItem.controlValues().begin(); i != pedalBoardItem.controlValues().end(); ++i) { auto &v = (*i); int index = GetControlIndex(v.key()); if (index != -1) { this->controlValues[index] = v.value(); } } PreparePortIndices(); ConnectControlPorts(); } void Lv2Effect::ConnectControlPorts() { // shared_ptr is not thread-safe. // Get naked pointers to use on the realtime thread. int controlArrayLength = 0; for (int i = 0; i < info->ports().size(); ++i) { if (info->ports()[i]->index() >= controlArrayLength) { controlArrayLength = info->ports()[i]->index() + 1; } } this->realtimePortInfo.resize(controlArrayLength); for (int i = 0; i < info->ports().size(); ++i) { const auto &port = info->ports()[i]; if (port->is_control_port()) { int index = port->index(); realtimePortInfo[index] = port.get(); lilv_instance_connect_port(pInstance, i, &this->controlValues[index]); } } } void Lv2Effect::PreparePortIndices() { for (int i = 0; i < info->ports().size(); ++i) { const auto &port = info->ports()[i]; int portIndex = port->index(); if (port->is_audio_port()) { if (port->is_input()) { this->inputAudioPortIndices.push_back(portIndex); } else { this->outputAudioPortIndices.push_back(portIndex); } } else if (port->is_atom_port()) { if (port->is_input()) { if (port->supports_midi()) { this->inputMidiPortIndices.push_back(portIndex); } this->inputAtomPortIndices.push_back(portIndex); } else { this->outputAtomPortIndices.push_back(portIndex); if (port->supports_midi()) { this->outputMidiPortIndices.push_back(portIndex); } } } } inputAudioBuffers.resize(inputAudioPortIndices.size()); outputAudioBuffers.resize(outputAudioPortIndices.size()); inputAtomBuffers.resize(inputAtomPortIndices.size()); outputAtomBuffers.resize(outputAtomPortIndices.size()); } void Lv2Effect::SetAudioInputBuffer(int index, float *buffer) { if (index >= inputAudioPortIndices.size()) { throw PiPedalArgumentException("Buffer index out of range."); } this->inputAudioBuffers[index] = buffer; int pluginIndex = this->inputAudioPortIndices[index]; lilv_instance_connect_port(this->pInstance, pluginIndex, buffer); } void Lv2Effect::SetAudioInputBuffer(float *left) { if (GetNumberOfInputs() > 1) { SetAudioInputBuffer(0, left); SetAudioInputBuffer(1, left); } else { SetAudioInputBuffer(0, left); } } void Lv2Effect::SetAudioInputBuffers(float *left, float *right) { if (GetNumberOfInputs() == 1) { SetAudioInputBuffer(0, left); } else { SetAudioInputBuffer(0, left); SetAudioInputBuffer(1, right); } } void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer) { this->outputAudioBuffers[index] = buffer; int pluginIndex = this->outputAudioPortIndices[index]; lilv_instance_connect_port(pInstance, pluginIndex, buffer); } int Lv2Effect::GetControlIndex(const std::string &key) const { for (int i = 0; i < info->ports().size(); ++i) { auto &port = info->ports()[i]; if (port->symbol() == key) return port->index(); } return -1; } Lv2Effect::~Lv2Effect() { if (worker) { worker = nullptr; // delete the worker first! } if (pInstance) { lilv_instance_free(pInstance); pInstance = nullptr; } if (work_schedule_feature) { free(work_schedule_feature->data); free(work_schedule_feature); } } void Lv2Effect::Activate() { this->AssignUnconnectedPorts(); lilv_instance_activate(pInstance); this->BypassTo(this->bypass ? 1.0f : 0.0f); } void Lv2Effect::AssignUnconnectedPorts() { for (int i = 0; i < this->GetNumberOfInputAudioPorts(); ++i) { if (GetAudioInputBuffer(i) == nullptr) { int pluginIndex = this->inputAudioPortIndices[i]; float *buffer = bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize()); lilv_instance_connect_port(pInstance, pluginIndex, buffer); } } for (int i = 0; i < this->GetNumberOfOutputAudioPorts(); ++i) { if (GetAudioOutputBuffer(i) == nullptr) { int pluginIndex = this->outputAudioPortIndices[i]; float *buffer = bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize()); lilv_instance_connect_port(pInstance, pluginIndex, buffer); } } for (int i = 0; i < this->GetNumberOfInputAtomPorts(); ++i) { if (GetAtomInputBuffer(i) == nullptr) { int pluginIndex = this->inputAtomPortIndices[i]; uint8_t *buffer = bufferPool.AllocateBuffer(pHost->GetAtomBufferSize()); lilv_instance_connect_port(pInstance, pluginIndex, buffer); ResetInputAtomBuffer((char *)buffer); this->inputAtomBuffers[i] = (char *)buffer; } } for (int i = 0; i < this->GetNumberOfOutputAtomPorts(); ++i) { if (GetAtomOutputBuffer(i) == nullptr) { int pluginIndex = this->outputAtomPortIndices[i]; uint8_t *buffer = bufferPool.AllocateBuffer(pHost->GetAtomBufferSize()); ResetOutputAtomBuffer((char *)buffer); lilv_instance_connect_port(pInstance, pluginIndex, buffer); this->outputAtomBuffers[i] = (char *)buffer; } } } void Lv2Effect::Deactivate() { lilv_instance_deactivate(pInstance); } static inline void CopyBuffer(float *input, float *output, uint32_t frames) { for (uint32_t i = 0; i < frames; ++i) { output[i] = input[i]; } } void Lv2Effect::Run(uint32_t samples,uint64_t instanceId, RealtimeRingBufferWriter *realtimeRingBufferWriter) { // close off the atom input frame. if (this->inputAtomBuffers.size() != 0) { lv2_atom_forge_pop(&this->inputForgeRt, &input_frame); } if (worker) { // relay worker response worker->EmitResponses(); } lilv_instance_run(pInstance, samples); // do soft bypass. if (this->bypassSamplesRemaining == 0) { if (this->currentBypass == 0) { // replace the contents of the output buffer(s) with the input buffer(s). if (this->outputAudioBuffers.size() == 1) { CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples); } else { if (this->inputAudioBuffers.size() == 1) { CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples); CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[1], samples); } } } // else leave the output alone. } else { double currentBypass = this->currentBypass; double currentBypassDx = this->currentBypassDx; int32_t bypassSamplesRemaining = (int)this->bypassSamplesRemaining; if (this->outputAudioBuffers.size() == 1) { float *input = this->inputAudioBuffers[0]; float *output = this->outputAudioBuffers[0]; for (uint32_t i = 0; i < samples; ++i) { output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i]; if (--bypassSamplesRemaining == 0) { currentBypassDx = 0; currentBypass = this->targetBypass; } currentBypass += currentBypassDx; } } else { float *inputL; float *inputR; if (this->inputAudioBuffers.size() == 1) { inputL = inputR = inputAudioBuffers[0]; } else { inputL = inputAudioBuffers[0]; inputR = inputAudioBuffers[1]; } float *outputL = outputAudioBuffers[0]; float *outputR = outputAudioBuffers[1]; for (uint32_t i = 0; i < samples; ++i) { outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i]; outputR[i] = currentBypass * outputR[i] + (1 - currentBypass) * inputR[i]; if (--bypassSamplesRemaining == 0) { currentBypassDx = 0; currentBypass = this->targetBypass; } currentBypass += currentBypassDx; } } if (bypassSamplesRemaining <= 0) { this->bypassSamplesRemaining = 0; this->currentBypass = this->targetBypass; this->currentBypassDx = 0; } else { this->currentBypass = currentBypass; this->currentBypassDx = currentBypassDx; this->bypassSamplesRemaining = bypassSamplesRemaining; } } RelayOutputMessages(instanceId,realtimeRingBufferWriter); } LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data) { Lv2Effect *this_ = (Lv2Effect *)handle; this_->worker->ScheduleWork(size, data); return LV2_WORKER_SUCCESS; } struct BufferHeader { uint32_t size; uint32_t type; }; void Lv2Effect::ResetInputAtomBuffer(char *data) { BufferHeader *header = (BufferHeader *)data; header->size = sizeof(LV2_Atom_Sequence_Body); header->type = uris.atom_Sequence; } void Lv2Effect::ResetOutputAtomBuffer(char *data) { BufferHeader *header = (BufferHeader *)data; header->size = pHost->GetAtomBufferSize() - 8; header->type = uris.atom_Chunk; } void Lv2Effect::BypassTo(float targetValue) { this->targetBypass = targetValue; double dx = targetValue - this->currentBypass; if (dx != 0) { this->bypassSamplesRemaining = (int)(bypassStartingSamples * std::abs(dx)); if (this->bypassStartingSamples == 0) { currentBypassDx = 0; this->currentBypass = targetBypass; } else { this->currentBypassDx = dx / this->bypassSamplesRemaining; } } } void Lv2Effect::ResetAtomBuffers() { for (size_t i = 0; i < this->inputAtomBuffers.size(); ++i) { ResetInputAtomBuffer(this->inputAtomBuffers[i]); } for (size_t i = 0; i < this->outputAtomBuffers.size(); ++i) { ResetOutputAtomBuffer(this->outputAtomBuffers[i]); } if (inputAtomBuffers.size() != 0) { const uint32_t notify_capacity = pHost->GetAtomBufferSize(); lv2_atom_forge_set_buffer( &(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers[0]), notify_capacity); // Start a sequence in the notify input port. lv2_atom_forge_sequence_head(&this->inputForgeRt, &input_frame, uris.unitsFrame); } } void Lv2Effect::RequestParameter(LV2_URID uridUri) { lv2_atom_forge_frame_time(&inputForgeRt, 0); LV2_Atom_Forge_Frame objectFrame; LV2_Atom_Forge_Ref set = lv2_atom_forge_object(&inputForgeRt, &objectFrame, 0, uris.patch_Get); lv2_atom_forge_key(&inputForgeRt, uris.patch_property); lv2_atom_forge_urid(&inputForgeRt, uridUri); lv2_atom_forge_pop(&inputForgeRt, &objectFrame); } void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) { LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); if (controlOutput == nullptr) { return; } LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev) { // frame_offset = ev->time.frames; // not really interested. if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) { const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; if (obj->body.otype != uris.patch_Set) // patch_Set is handled elsewhere. { realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj); } } } } void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest) { LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev) { // frame_offset = ev->time.frames; // not really interested. if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) { const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; if (obj->body.otype == uris.patch_Set) { // Get the property and value of the set message const LV2_Atom *property = NULL; const LV2_Atom *value = NULL; lv2_atom_object_get( obj, uris.patch_property, &property, uris.patch_value, &value, 0); if (!property) { } else if (property->type != uris.atom_URID) { } else { LV2_URID key = ((const LV2_Atom_URID *)property)->body; if (key == pRequest->uridUri) { int atom_size = value->size + sizeof(LV2_Atom); if (atom_size > sizeof(pRequest->response)) { pRequest->errorMessage = "Response is too large."; } else { pRequest->responseLength = atom_size; memcpy(pRequest->response,value,atom_size); } break; } } } } } } std::string Lv2Effect::GetAtomObjectType(uint8_t*pData) { LV2_Atom_Object *pAtom = (LV2_Atom_Object*)pData; if (pAtom->atom.type != uris.atom_Object) { throw std::invalid_argument("Not an Lv2 Object"); } return pHost->Lv2UriudToString(pAtom->body.otype); } std::string Lv2Effect::AtomToJson(uint8_t*pData) { std::stringstream s; json_writer writer(s); LV2_Atom *pAtom = (LV2_Atom*)pData; WriteAtom(writer,pAtom); return s.str(); } static std::string UriToFieldName(const std::string&uri){ int pos; for (pos = uri.length(); pos >= 0; --pos) { char c = uri[pos]; if (c == '#' || c == '/' || c == ':') { break; } } return uri.substr(pos+1); } void Lv2Effect::WriteAtom(json_writer &writer, LV2_Atom*pAtom) { if (pAtom->type == uris.atom_Blank) { writer.write_raw("null"); } else if (pAtom->type == uris.atom_float) { writer.write( ((LV2_Atom_Float*)pAtom)->body ); } else if (pAtom->type == uris.atom_Int) { writer.write( ((LV2_Atom_Int*)pAtom)->body ); } else if (pAtom->type == uris.atom_Long) { writer.write( ((LV2_Atom_Long*)pAtom)->body ); } else if (pAtom->type == uris.atom_Double) { writer.write( ((LV2_Atom_Double*)pAtom)->body ); } else if (pAtom->type == uris.atom_Bool) { writer.write( ((LV2_Atom_Bool*)pAtom)->body ); } else if (pAtom->type == uris.atom_String) { const char *p = (((const char*) pAtom) + sizeof(LV2_Atom_String)); writer.write( p ); } else if (pAtom->type == uris.atom_Vector) { LV2_Atom_Vector *pVector = (LV2_Atom_Vector*)pAtom; writer.start_array(); { size_t n = (pAtom->size-sizeof(pVector->body))/pVector->body.child_size; char *pItems = ((char*)pAtom) + sizeof(LV2_Atom_Vector); if (pVector->body.child_type == uris.atom_float) { float *p = (float*)pItems; for (size_t i = 0; i < n; ++i) { if (i != 0) writer.write_raw(","); writer.write(*p++); } } else if (pVector->body.child_type == uris.atom_Int) { int32_t *p = (int32_t*)pItems; for (size_t i = 0; i < n; ++i) { if (i != 0) writer.write_raw(","); writer.write(*p++); } } else if (pVector->body.child_type == uris.atom_Long) { int64_t *p = (int64_t*)pItems; for (size_t i = 0; i < n; ++i) { if (i != 0) writer.write_raw(","); writer.write(*p++); } } else if (pVector->body.child_type == uris.atom_Double) { double *p = (double*)pItems; for (size_t i = 0; i < n; ++i) { if (i != 0) writer.write_raw(","); writer.write(*p++); } } else if (pVector->body.child_type == uris.atom_Bool) { bool *p = (bool*)pItems; for (size_t i = 0; i < n; ++i) { if (i != 0) writer.write_raw(","); writer.write(*p++); } } } writer.end_array(); } else if (pAtom->type == uris.atom_Object) { writer.start_object(); const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom; bool firstMember = true; if (obj->body.id != 0) { std::string id = pHost->Lv2UriudToString(obj->body.id); writer.write_member("id",id.c_str()); firstMember = false; } if (obj->body.otype != 0) { std::string type = pHost->Lv2UriudToString(obj->body.otype); writer.write_member("lv2Type",type.c_str()); if (!firstMember) { writer.write_raw(","); } firstMember = false; } LV2_ATOM_OBJECT_FOREACH (obj, prop) { if (!firstMember) { writer.write_raw(","); } firstMember = false; std::string key = pHost->Lv2UriudToString(prop->key); key = UriToFieldName(key); writer.write(key); writer.write_raw(": "); LV2_Atom *value = &(prop->value); WriteAtom(writer,value); } writer.end_object(); } }