diff --git a/src/IEffect.hpp b/src/IEffect.hpp index b5a4ce7..beec4ee 100644 --- a/src/IEffect.hpp +++ b/src/IEffect.hpp @@ -54,7 +54,9 @@ namespace pipedal { virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs. virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs. + virtual int GetNumberOfSidechainAudioBuffers() const { return 0; } virtual float *GetAudioInputBuffer(int index) const = 0; + virtual float *GetAudioSidechainBuffer(int index) const { throw std::runtime_error("Not implemented"); } virtual float *GetAudioOutputBuffer(int index) const = 0; virtual void ResetAtomBuffers() = 0; @@ -65,6 +67,7 @@ namespace pipedal { virtual void SetAudioInputBuffer(int index, float *buffer) = 0; virtual void SetAudioOutputBuffer(int index, float*buffer) = 0; + virtual void SetAudioSidechainBuffer(int index, float *buffer) { throw std::runtime_error("Not implemented");} virtual void Activate() = 0; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index fd339b0..4551259 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -384,7 +384,12 @@ void Lv2Effect::PreparePortIndices() { if (port->is_input()) { - this->inputAudioPortIndices.push_back(portIndex); + if (port->is_sidechain()) + { + this->inputSidechainPortIndices.push_back(portIndex); + } else { + this->inputAudioPortIndices.push_back(portIndex); + } } else { @@ -432,6 +437,7 @@ void Lv2Effect::PreparePortIndices() this->maxInputControlPort = maxInputControlPort; inputAudioBuffers.resize(inputAudioPortIndices.size()); + inputSidechainBuffers.resize(inputSidechainPortIndices.size()); outputAudioBuffers.resize(outputAudioPortIndices.size()); inputAtomBuffers.resize(inputAtomPortIndices.size()); outputAtomBuffers.resize(outputAtomPortIndices.size()); @@ -439,9 +445,7 @@ void Lv2Effect::PreparePortIndices() if (RequiresBufferStaging()) { EnableBufferStaging( - GetStagedBufferSize(), - this->GetNumberOfInputAudioBuffers(), - this->GetNumberOfOutputAudioBuffers()); + GetStagedBufferSize()); } } @@ -473,6 +477,7 @@ void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize) } } + void Lv2Effect::SetAudioInputBuffer(int index, float *buffer) { this->inputAudioBuffers.at(index) = buffer; @@ -512,6 +517,32 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer) // } } } +void Lv2Effect::SetAudioSidechainBuffer(int index, float *buffer) +{ + this->inputSidechainBuffers.at(index) = buffer; + + if (borrowedEffect) + { + // Already running on the realtime thread, + // so don't update the audio ports until the effect gets placed on the realtime thread. + return; + } + + if (stagingBufferSize != 0) + { + int pluginIndex = this->inputSidechainPortIndices.at(index); + if (index >= sidechainStagingBufferPointers.size()) + { + throw std::runtime_error("Invalid input staging buffer index."); + } + lilv_instance_connect_port(this->pInstance, pluginIndex, sidechainStagingBufferPointers.at(index)); + } + else + { + int pluginIndex = this->inputSidechainPortIndices.at(index); + lilv_instance_connect_port(this->pInstance, pluginIndex, buffer); + } +} void Lv2Effect::SetAudioInputBuffer(float *left) { @@ -664,6 +695,14 @@ void Lv2Effect::UpdateAudioPorts() lilv_instance_connect_port(pInstance, portIndex, outputStagingBufferPointers.at(i)); } } + for (size_t i = 0; i < this->inputSidechainPortIndices.size(); ++i) + { + int portIndex = this->inputSidechainPortIndices.at(i); + if (sidechainStagingBufferPointers.at(i) != nullptr) + { + lilv_instance_connect_port(pInstance, portIndex, sidechainStagingBufferPointers.at(i)); + } + } for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i) { if (i == 0) @@ -710,6 +749,15 @@ void Lv2Effect::UpdateAudioPorts() lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i)); } } + for (size_t i = 0; i < this->inputSidechainPortIndices.size(); ++i) + { + int portIndex = this->inputSidechainPortIndices.at(i); + if (GetAudioSidechainBuffer(i) != nullptr) + { + lilv_instance_connect_port(pInstance, portIndex, GetAudioSidechainBuffer(i)); + } + } + for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i) { auto atomInputBuffer = this->GetAtomInputBuffer(i); @@ -877,6 +925,16 @@ size_t Lv2Effect::stageToInput(size_t inputSampleOffset, size_t samples) pOutput[i] = pInput[i]; } } + for (size_t nSidechain = 0; nSidechain < this->inputSidechainBuffers.size(); ++nSidechain) + { + float *restrict pInput = this->inputSidechainBuffers[nSidechain] + inputSampleOffset; + float *restrict pOutput = this->sidechainStagingBufferPointers.at(nSidechain) + this->stagingInputIx; + for (size_t i = 0; i < thisTime; ++i) + { + pOutput[i] = pInput[i]; + } + + } this->stagingInputIx += thisTime; inputSampleOffset += thisTime; @@ -1487,8 +1545,11 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std:: mainThreadPathProperties[propertyUri] = jsonAtom; } -void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs) +void Lv2Effect::EnableBufferStaging(size_t bufferSize ) { + size_t nInputs = this->GetNumberOfInputAudioBuffers(); + size_t nSidechainInputs = this->GetNumberOfSidechainAudioBuffers(); + size_t nOutputs = this->GetNumberOfOutputAudioBuffers(); stagingBufferSize = bufferSize; stagingOutputIx = bufferSize; @@ -1520,6 +1581,12 @@ void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nO inputStagingBuffers[i][bufferSize] = 99.9f; // guard entry inputStagingBufferPointers.at(i) = inputStagingBuffers.at(i).data(); } + for (size_t i = 0; i < nSidechainInputs; ++i) + { + sidechainStagingBuffers.at(i).resize(bufferSize + 1); + sidechainStagingBuffers[i][bufferSize] = 99.9f; // guard entry + sidechainStagingBufferPointers.at(i) = sidechainStagingBuffers.at(i).data(); + } for (size_t i = 0; i < nOutputs; ++i) { outputStagingBuffers.at(i).resize(bufferSize + 1); @@ -1571,6 +1638,12 @@ float *Lv2Effect::GetAudioInputBuffer(int index) const throw std::range_error("Lv2Effect::GetAudioInputBuffer"); return this->inputAudioBuffers.at(index); } +float *Lv2Effect::GetAudioSidechainBuffer(int index) const +{ + if (index < 0 || index >= this->inputSidechainBuffers.size()) + throw std::range_error("Lv2Effect::GetAudioSidechainBuffer"); + return this->inputSidechainBuffers.at(index); +} float *Lv2Effect::GetAudioOutputBuffer(int index) const { diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index 4abba0d..76c577c 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -81,6 +81,8 @@ namespace pipedal std::vector inputAudioPortIndices; std::vector outputAudioPortIndices; + std::vector inputSidechainPortIndices; + std::vector inputAtomPortIndices; std::vector outputAtomPortIndices; @@ -90,6 +92,7 @@ namespace pipedal std::vector midiInputIndices; std::vector inputAudioBuffers; + std::vector inputSidechainBuffers; std::vector outputAudioBuffers; std::vector inputAtomBuffers; @@ -212,7 +215,7 @@ namespace pipedal bool borrowedEffect = false; bool activated = false; - void EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs); + void EnableBufferStaging(size_t bufferSize); void CheckStagingBufferSentries(); public: @@ -262,8 +265,10 @@ namespace pipedal size_t stagingInputIx = 0; size_t stagingOutputIx = 0; std::vector> inputStagingBuffers; + std::vector> sidechainStagingBuffers; std::vector> outputStagingBuffers; std::vector inputStagingBufferPointers; + std::vector sidechainStagingBufferPointers; std::vector outputStagingBufferPointers; std::vector stagedInputAtomBuffer; @@ -297,22 +302,26 @@ namespace pipedal virtual void ResetAtomBuffers(); virtual uint64_t GetInstanceId() const { return instanceId; } - virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); } - virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); } + virtual int GetNumberOfInputAudioPorts() const override { return inputAudioPortIndices.size(); } + virtual int GetNumberOfOutputAudioPorts() const override { return outputAudioPortIndices.size(); } - virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); } + virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); } - virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); } - virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); } - virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); } + virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); } + virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); } + virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); } - virtual int GetNumberOfInputAudioBuffers() const { return this->inputAudioBuffers.size(); } - virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); } + virtual int GetNumberOfInputAudioBuffers() const override { return this->inputAudioBuffers.size(); } + virtual int GetNumberOfSidechainAudioBuffers() const override { return this->inputSidechainBuffers.size(); } + virtual int GetNumberOfOutputAudioBuffers() const override {return this->outputAudioBuffers.size(); } - virtual void SetAudioInputBuffer(int index, float *buffer); - virtual float *GetAudioInputBuffer(int index) const; + virtual void SetAudioInputBuffer(int index, float *buffer) override; + virtual float *GetAudioInputBuffer(int index) const override; + + virtual void SetAudioSidechainBuffer(int index, float *buffer) override; + virtual float *GetAudioSidechainBuffer(int index) const override; virtual void SetAudioInputBuffer(float *buffer); diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index 780ce82..ede9b78 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -176,6 +176,60 @@ std::vector Lv2Pedalboard::PrepareItems( auto bufferR = inputBuffers[1]; } } + // Connect sidechain buffers. + + if (pLv2Effect->GetNumberOfSidechainAudioBuffers() != 0) + { + if (item.sideChainInputId() == -2) // -2 means "use pedalboard inputs". + { + for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i) + { + if (i < this->pedalboardInputBuffers.size()) + { + pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[i]); + } + else + { + // just use the first output buffer for all sidechain inputs. + pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[0]); + } + } + } else if (item.sideChainInputId() != -1) { + IEffect *pSideChainInput = GetEffect(item.sideChainInputId()); + + if (pSideChainInput) + { + for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i) + { + if (i < pSideChainInput->GetNumberOfOutputAudioBuffers()) + { + pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(i)); + } + else + { + // just use the first output buffer for all sidechain inputs. + pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(0)); + } + } + } else { + throw std::runtime_error("Internal error: Sidechain input IEffect not found."); + } + } + else + { + // No sidechain input. Feed zero buffer to all sidechain audio ports.. + for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i) + { + + // just use one buffer for all plugins + if (!this->pedalboardSidechainBuffer) + { + this->pedalboardSidechainBuffer = CreateNewAudioBuffer(); + } + pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardSidechainBuffer); + } + } + } // check to see whether we need buffer staging. bool requiresBufferStaging = false; @@ -194,7 +248,6 @@ std::vector Lv2Pedalboard::PrepareItems( } } - if (!requiresBufferStaging) { this->processActions.push_back( diff --git a/src/Lv2Pedalboard.hpp b/src/Lv2Pedalboard.hpp index 5cb3fe9..961b3e4 100644 --- a/src/Lv2Pedalboard.hpp +++ b/src/Lv2Pedalboard.hpp @@ -57,6 +57,7 @@ namespace pipedal BufferPool bufferPool; std::vector pedalboardInputBuffers; std::vector pedalboardOutputBuffers; + float *pedalboardSidechainBuffer = nullptr; std::vector> effects; std::vector realtimeEffects; diff --git a/src/Pedalboard.cpp b/src/Pedalboard.cpp index 89324c9..3c8ad17 100644 --- a/src/Pedalboard.cpp +++ b/src/Pedalboard.cpp @@ -534,6 +534,7 @@ JSON_MAP_BEGIN(PedalboardItem) JSON_MAP_REFERENCE(PedalboardItem,title) JSON_MAP_REFERENCE(PedalboardItem,useModUi) JSON_MAP_REFERENCE(PedalboardItem,iconColor) + JSON_MAP_REFERENCE(PedalboardItem,sideChainInputId) JSON_MAP_END() diff --git a/src/Pedalboard.hpp b/src/Pedalboard.hpp index 995ddcb..173b497 100644 --- a/src/Pedalboard.hpp +++ b/src/Pedalboard.hpp @@ -107,6 +107,7 @@ public: std::string title_; bool useModUi_ = false; std::string iconColor_; + int64_t sideChainInputId_ = -1; // non persistent state. PropertyMap patchProperties; @@ -138,6 +139,7 @@ public: GETTER_SETTER_REF(title) GETTER_SETTER_REF(iconColor) GETTER_SETTER(useModUi) + GETTER_SETTER(sideChainInputId) Lv2PluginState&lv2State() { return lv2State_; } // non-const version. GETTER_SETTER_REF(lilvPresetUri) diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 831de12..70adb50 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -2428,7 +2428,7 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived( auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri); if (i != pedalboardItem->pathProperties_.end()) { - std::string abstractAtomString = storage.ToAbstractPathJson(atomString); + std::string abstractAtomString = storage.ToAbstractPathFromJson(atomString); pedalboardItem->pathProperties_[pathPatchPropertyUri] = abstractAtomString; std::vector t{subscribers.begin(), subscribers.end()}; diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index d4d7bd2..60ddc35 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -121,6 +121,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld) enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration); core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled); core__connectionOptional = lilv_new_uri(pWorld, LV2_CORE__connectionOptional); + core__isSideChain = lilv_new_uri(pWorld, LV2_CORE_PREFIX "isSideChain"); // missing in lv2.h portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI); portprops__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger); midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent); @@ -134,6 +135,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld) atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType); atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path); + atom__String = lilv_new_uri(pWorld, LV2_ATOM__String); presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset); state__state = lilv_new_uri(pWorld, LV2_STATE__state); rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label"); @@ -214,6 +216,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld) buf_size__maxBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__maxBlockLength); buf_size__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength); buf_size__coarseBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__coarseBlockLength); + port_groups__sideChainOf = lilv_new_uri(pWorld, LV2_PORT_GROUPS__sideChainOf); } @@ -794,7 +797,12 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin } else { - unsupportedPatchProperty = true; + if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->rdfs__range, lv2Host->lilvUris->atom__String)) + { + + } else { + unsupportedPatchProperty = true; + } } } } @@ -945,6 +953,60 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP std::sort(ports_.begin(), ports_.end(), ports_sort_compare); + + + // Check the portgroup for audio inputs to see if they are sidechains. + std::string last_sidechain_title; + for (auto&port: this->ports_) + { + if (port->is_audio_port() && port->is_input()) { + if (!port->port_group().empty()) + { + for (auto &pg: this->port_groups_) + { + if (pg->uri() == port->port_group()) + { + if (!pg->sideChainOf().empty()) + { + port->is_sidechain(true); + audio_sidechain_title_ = pg->name(); + if (last_sidechain_title.empty()) { + last_sidechain_title = pg->name(); + } + else if (last_sidechain_title != pg->name()) { + // multiple sidechain titles! + isValid = false; + Lv2Log::debug("Plugin %s (%s) has multiple sidechain titles (%s and %s).", + this->name_.c_str(), + this->uri_.c_str(), + last_sidechain_title.c_str(), + pg->name().c_str()); + } + } + break; + } + } + } else { + if (port->is_sidechain()) { + // port is marked as sidechain, but has no port group. + if (last_sidechain_title.empty()) + { + last_sidechain_title = port->name(); + this->audio_sidechain_title_ = port->name(); + } + else if (last_sidechain_title != port->name()) { + isValid = false; + Lv2Log::debug("Plugin %s (%s) has multiple sidechain ports, but no portgroup. (%s)", + this->name_.c_str(), + this->uri_.c_str(), + port->name().c_str()); + } + } + } + + } + } + // Fetch patch properties. { AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__writable, nullptr); @@ -1034,17 +1096,28 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP } int nInputs = 0; + int nSideChains = 0; for (size_t i = 0; i < ports_.size(); ++i) { auto port = ports_[i]; if (port->is_audio_port() && port->is_input()) { - if (nInputs >= 2 && !port->connection_optional()) - { - isValid = false; - break; + if (!port->is_sidechain()) { + if (nInputs >= 2) + { + isValid = false; + Lv2Log::debug("Plugin %s (%s) has more than 2 audio inputs.", this->name_.c_str(), this->uri_.c_str()); + break; + } + ++nInputs; + } else { + if (nSideChains >= 2) { + Lv2Log::debug("Plugin %s (%s) has more than 2 audio sidechain inputs.", this->name_.c_str(), this->uri_.c_str()); + isValid = false; + break; + } + ++nSideChains; } - ++nInputs; } } int nOutputs = 0; @@ -1199,6 +1272,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri); this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional); this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger); + this->is_sidechain_ = lilv_port_has_property(plugin,pPort,host->lilvUris->core__isSideChain); AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor); if (port_ledColor) @@ -1402,7 +1476,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin) } else if (port->is_audio_port()) { - ++audio_inputs_; + if (port->is_sidechain()) { + // sidechain outputs don't count. + ++audio_side_chain_inputs_; + } else { + ++audio_inputs_; + } } } else if (port->is_output()) @@ -1918,12 +1997,29 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri) { LilvWorld *pWorld = lv2Host->pWorld; + // Enumerate all Lilv nodes for groupUri that have an 'a' predicate + this->uri_ = groupUri; AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str()); + + + AutoLilvNode aPredicate = lilv_new_uri(pWorld, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + + AutoLilvNodes types = lilv_world_find_nodes(pWorld, uri, aPredicate, nullptr); + LILV_FOREACH(nodes, iNode, types) + { + AutoLilvNode typeNode = lilv_nodes_get(types, iNode); + std::string typeUri = nodeAsString(typeNode); + this->isA_.push_back(std::move(typeUri)); + } + + AutoLilvNode symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr); symbol_ = nodeAsString(symbolNode); AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr); name_ = nodeAsString(nameNode); + AutoLilvNode sideChainOfNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->port_groups__sideChainOf, nullptr); + sideChainOf_ = nodeAsString(sideChainOfNode); } bool Lv2PluginInfo::isSplit() const @@ -2187,6 +2283,7 @@ json_map::storage_type Lv2PortInfo::jmap{ json_map::reference("is_input", &Lv2PortInfo::is_input_), json_map::reference("is_output", &Lv2PortInfo::is_output_), + json_map::reference("is_sidechain", &Lv2PortInfo::is_sidechain_), json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_), json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_), @@ -2226,7 +2323,11 @@ json_map::storage_type Lv2PortInfo::jmap{ json_map::storage_type Lv2PortGroup::jmap{{ + MAP_REF(Lv2PortGroup, isA), MAP_REF(Lv2PortGroup, uri), + MAP_REF(Lv2PortGroup, name), + MAP_REF(Lv2PortGroup, symbol), + MAP_REF(Lv2PortGroup, sideChainOf), }}; @@ -2281,6 +2382,7 @@ json_map::storage_type Lv2PluginUiPort::jmap{{ json_map::reference("name", &Lv2PluginUiPort::name_), MAP_REF(Lv2PluginUiPort, index), MAP_REF(Lv2PluginUiPort, is_input), + MAP_REF(Lv2PluginUiPort, is_sidechain), MAP_REF(Lv2PluginUiPort, min_value), MAP_REF(Lv2PluginUiPort, max_value), MAP_REF(Lv2PluginUiPort, default_value), @@ -2324,7 +2426,9 @@ json_map::storage_type json_map::reference("author_name", &Lv2PluginUiInfo::author_name_), json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_), json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_), + json_map::reference("audio_side_chain_inputs", &Lv2PluginUiInfo::audio_side_chain_inputs_), json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_), + json_map::reference("audio_side_chain_title", &Lv2PluginUiInfo::audio_side_chain_title_), json_map::reference("description", &Lv2PluginUiInfo::description_), json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_), json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_), diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 2712bad..ef9e220 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -33,7 +33,7 @@ #include #include "ModGui.hpp" -//#include "lv2.h" +// #include "lv2.h" #include "Units.hpp" #include "PluginPreset.hpp" @@ -206,6 +206,7 @@ namespace pipedal bool is_control_port_ = false; bool is_audio_port_ = false; + bool is_sidechain_ = false; bool is_atom_port_ = false; bool is_cv_port_ = false; bool connection_optional_ = false; @@ -287,6 +288,7 @@ namespace pipedal LV2_PROPERTY_GETSET_SCALAR(is_output); LV2_PROPERTY_GETSET_SCALAR(is_control_port); LV2_PROPERTY_GETSET_SCALAR(is_audio_port); + LV2_PROPERTY_GETSET_SCALAR(is_sidechain); LV2_PROPERTY_GETSET_SCALAR(is_atom_port); LV2_PROPERTY_GETSET_SCALAR(connection_optional); LV2_PROPERTY_GETSET_SCALAR(is_cv_port); @@ -321,7 +323,6 @@ namespace pipedal return Lv2BufferType::Unknown; } - public: Lv2PortInfo() {} ~Lv2PortInfo() = default; @@ -330,7 +331,8 @@ namespace pipedal static json_map::storage_type jmap; }; - class Lv2PatchPropertyInfo { + class Lv2PatchPropertyInfo + { private: std::string uri_; @@ -343,9 +345,10 @@ namespace pipedal std::string shortName_; std::vector fileTypes_; std::vector supportedExtensions_; + public: Lv2PatchPropertyInfo() {} - Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri); + Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri); LV2_PROPERTY_GETSET(uri); LV2_PROPERTY_GETSET(writable); @@ -359,21 +362,24 @@ namespace pipedal LV2_PROPERTY_GETSET(supportedExtensions); DECLARE_JSON_MAP(Lv2PatchPropertyInfo); - }; class Lv2PortGroup { private: + std::vector isA_; std::string uri_; std::string symbol_; std::string name_; + std::string sideChainOf_; public: + LV2_PROPERTY_GETSET(isA); LV2_PROPERTY_GETSET(uri); LV2_PROPERTY_GETSET(symbol); LV2_PROPERTY_GETSET(name); - + LV2_PROPERTY_GETSET(sideChainOf); + Lv2PortGroup() {} Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri); @@ -389,10 +395,11 @@ namespace pipedal using ptr = std::shared_ptr; Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo() {} - bool isSplit() const ; + bool isSplit() const; private: - struct FindWritablePathPropertiesResult { + struct FindWritablePathPropertiesResult + { std::shared_ptr pipedalUi; bool hasUnsupportedPatchProperties = false; }; @@ -406,7 +413,7 @@ namespace pipedal std::string name_; std::string plugin_class_; std::string brand_; - std::string label_; + std::string label_; std::vector supported_features_; std::vector required_features_; std::vector optional_features_; @@ -420,9 +427,9 @@ namespace pipedal std::vector> ports_; std::vector> port_groups_; std::vector patchProperties_; - + std::string audio_sidechain_title_; bool hasDefaultState_; - + bool is_valid_ = false; PiPedalUI::ptr piPedalUI_; ModGui::ptr modGui_; @@ -463,6 +470,7 @@ namespace pipedal LV2_PROPERTY_GETSET(minBlockLength) LV2_PROPERTY_GETSET(maxBlockLength) LV2_PROPERTY_GETSET(powerOf2BlockLength) + LV2_PROPERTY_GETSET(audio_sidechain_title) bool WantsWorkerThread() const; @@ -529,9 +537,11 @@ namespace pipedal class Lv2PluginUiPortGroup { private: + std::vector isA_; std::string uri_; std::string symbol_; std::string name_; + std::string sideChainOf_; std::string parent_group_; int32_t program_list_id_ = -1; // used by VST3. @@ -546,9 +556,7 @@ namespace pipedal public: Lv2PluginUiPortGroup() {} Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup) - : symbol_(pPortGroup->symbol()) - , name_(pPortGroup->name()) - , uri_(pPortGroup->uri()) + : isA_(pPortGroup->isA()), symbol_(pPortGroup->symbol()), name_(pPortGroup->name()), uri_(pPortGroup->uri()), sideChainOf_(pPortGroup->sideChainOf()) { } Lv2PluginUiPortGroup( @@ -570,14 +578,18 @@ namespace pipedal } Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort) : symbol_(pPort->symbol()), index_(pPort->index()), - is_input_(pPort->is_input()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()), + is_input_(pPort->is_input()), + is_sidechain_(pPort->is_sidechain()), + name_(pPort->name()), + min_value_(pPort->min_value()), + max_value_(pPort->max_value()), default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()), - is_logarithmic_(pPort->is_logarithmic()), - integer_property_(pPort->integer_property()), + is_logarithmic_(pPort->is_logarithmic()), + integer_property_(pPort->integer_property()), mod_momentaryOffByDefault_(pPort->mod_momentaryOffByDefault()), mod_momentaryOnByDefault_(pPort->mod_momentaryOnByDefault()), pipedal_graphicEq_(pPort->pipedal_graphicEq()), - + enumeration_property_(pPort->enumeration_property()), toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()), trigger_property_(pPort->trigger_property()), @@ -606,7 +618,7 @@ namespace pipedal } } } - is_bypass_ = + is_bypass_ = pPort->is_bypass() || name_ == "bypass" || name_ == "Bypass" || symbol_ == "bypass" || symbol_ == "Bypass"; } @@ -616,6 +628,7 @@ namespace pipedal int index_; std::string name_; bool is_input_ = true; + bool is_sidechain_ = false; float min_value_ = 0, max_value_ = 1, default_value_ = 0; bool is_logarithmic_ = false; int display_priority_ = -1; @@ -682,7 +695,8 @@ namespace pipedal std::string uri_; std::string name_; uint32_t minorVersion_ = 0; - uint32_t microVersion_ = 0;; + uint32_t microVersion_ = 0; + ; std::string brand_; std::string label_; std::string author_name_; @@ -690,12 +704,17 @@ namespace pipedal PluginType plugin_type_; std::string plugin_display_type_; int audio_inputs_ = 0; + int audio_side_chain_inputs_ = 0; int audio_outputs_ = 0; + std::string audio_side_chain_title_; int has_midi_input_ = false; int has_midi_output_ = false; std::string description_; bool is_vst3_ = false; + int64_t side_chain_input_id = -1; + + std::vector controls_; std::vector port_groups_; std::vector fileProperties_; @@ -704,7 +723,6 @@ namespace pipedal ModGui::ptr modGui_; std::vector patchProperties_; - public: LV2_PROPERTY_GETSET(uri) LV2_PROPERTY_GETSET(name) @@ -718,6 +736,7 @@ namespace pipedal LV2_PROPERTY_GETSET(plugin_display_type) LV2_PROPERTY_GETSET_SCALAR(audio_inputs) LV2_PROPERTY_GETSET_SCALAR(audio_outputs) + LV2_PROPERTY_GETSET(audio_side_chain_title) LV2_PROPERTY_GETSET_SCALAR(has_midi_input) LV2_PROPERTY_GETSET_SCALAR(has_midi_output) LV2_PROPERTY_GETSET_SCALAR(description) @@ -729,6 +748,7 @@ namespace pipedal LV2_PROPERTY_GETSET(uiPortNotifications) LV2_PROPERTY_GETSET(modGui) LV2_PROPERTY_GETSET(patchProperties) + LV2_PROPERTY_GETSET_SCALAR(audio_side_chain_inputs) static json_map::storage_type jmap; }; @@ -772,6 +792,7 @@ namespace pipedal AutoLilvNode enumeration_property_uri; AutoLilvNode core__toggled; AutoLilvNode core__connectionOptional; + AutoLilvNode core__isSideChain; AutoLilvNode portprops__not_on_gui_property_uri; AutoLilvNode portprops__trigger; AutoLilvNode midi__event; @@ -782,9 +803,9 @@ namespace pipedal AutoLilvNode invada_units__unit; // typo in invada plugins. AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins. - AutoLilvNode atom__bufferType; AutoLilvNode atom__Path; + AutoLilvNode atom__String; AutoLilvNode presets__preset; AutoLilvNode state__state; AutoLilvNode rdfs__label; @@ -816,7 +837,6 @@ namespace pipedal AutoLilvNode pipedalUI__width; AutoLilvNode pipedalUI__graphicEq; - AutoLilvNode pipedalUI__outputPorts; AutoLilvNode pipedalUI__text; @@ -846,7 +866,6 @@ namespace pipedal AutoLilvNode patch__readable; AutoLilvNode pipedal_patch__readable; - AutoLilvNode mod__brand; AutoLilvNode mod__label; AutoLilvNode mod__preferMomentaryOffByDefault; @@ -861,11 +880,9 @@ namespace pipedal AutoLilvNode buf_size__maxBlockLength; AutoLilvNode buf_size__fixedBlockLength; AutoLilvNode buf_size__coarseBlockLength; - - - + AutoLilvNode port_groups__sideChainOf; }; - LilvUris* lilvUris = nullptr; + LilvUris *lilvUris = nullptr; private: bool vst3Enabled = true; @@ -896,7 +913,7 @@ namespace pipedal void free_world(); std::vector> plugins_; - std::map> pluginsByUri; + std::map> pluginsByUri; std::vector ui_plugins_; std::map> classesMap; @@ -947,22 +964,19 @@ namespace pipedal public: virtual MapFeature &GetMapFeature() override { return this->mapFeature; } - void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory); + void CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory); - - - std::string MapResourcePath(const std::string&uri, const std::string&relativePath); + std::string MapResourcePath(const std::string &uri, const std::string &relativePath); void ReloadPlugins(); // equivalent to LV2 MapPath AbstractPath features. std::string MapPath(const std::string &abstractPath); - std::string AbstractPath(const std::string&path); - json_variant MapPath(const json_variant&json); - json_variant AbstractPath(const json_variant&json); + std::string AbstractPath(const std::string &path); + json_variant MapPath(const json_variant &json); + json_variant AbstractPath(const json_variant &json); private: - std::set pluginsThatHaveBeenCheckedForResources; void CheckForResourceInitization(const std::string pluginUri); @@ -973,12 +987,11 @@ namespace pipedal } static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type); static void StateRestoreCallback( - const char* port_symbol, - void* user_data, - const void* value, - uint32_t size, - uint32_t type); - + const char *port_symbol, + void *user_data, + const void *value, + uint32_t size, + uint32_t type); virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem); void LoadPluginClassesFromLilv(); @@ -995,9 +1008,9 @@ namespace pipedal IHost *asIHost() { return this; } - virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard,Lv2PedalboardErrorList &errorList); + virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList); - virtual Lv2Pedalboard *UpdateLv2PedalboardStructure(Pedalboard &pedalboard,Lv2Pedalboard *existingPedalboard,Lv2PedalboardErrorList &errorList); + virtual Lv2Pedalboard *UpdateLv2PedalboardStructure(Pedalboard &pedalboard, Lv2Pedalboard *existingPedalboard, Lv2PedalboardErrorList &errorList); void setSampleRate(double sampleRate) { @@ -1010,9 +1023,8 @@ namespace pipedal class Urids; - Urids *urids = nullptr; - ModGuiUris* mod_gui_uris = nullptr; - + Urids *urids = nullptr; + ModGuiUris *mod_gui_uris = nullptr; void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings); @@ -1021,7 +1033,7 @@ namespace pipedal std::shared_ptr GetLv2PluginClass() const; - const std::vector>& GetPlugins() const { return plugins_; } + const std::vector> &GetPlugins() const { return plugins_; } const std::vector &GetUiPlugins() const { return ui_plugins_; } virtual std::shared_ptr GetPluginInfo(const std::string &uri) const; diff --git a/src/Storage.cpp b/src/Storage.cpp index b6979ad..1a70acd 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -1717,7 +1717,7 @@ void Storage::ToAbstractPaths(PluginPreset &pluginPreset) std::map newPathProperties; for (const auto &pair : pluginPreset.pathProperties_) { - newPathProperties[pair.first] = this->ToAbstractPathJson(pair.second); + newPathProperties[pair.first] = this->ToAbstractPathFromJson(pair.second); } pluginPreset.pathProperties_ = newPathProperties; } @@ -2999,7 +2999,19 @@ std::string Storage::GetTone3000Auth() const return tone3000Auth; } -std::string Storage::ToAbstractPathJson(const std::string &pathJson) +std::filesystem::path Storage::FromAbstractPathString(const std::string &stringPath) +{ + if (stringPath.empty()) { + return ""; + } + fs::path path { stringPath}; + if (path.is_absolute()) { + return path; + } + return GetPluginUploadDirectory() / path; +} + +std::string Storage::ToAbstractPathFromJson(const std::string &pathJson) { json_variant v = json_variant::parse(pathJson); diff --git a/src/Storage.hpp b/src/Storage.hpp index 765bc6f..f6044fa 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -129,8 +129,9 @@ public: const std::filesystem::path &GetPluginUploadDirectory() const; - std::string ToAbstractPathJson(const std::string &jsonAtomPath); + std::string ToAbstractPathFromJson(const std::string &jsonAtomPath); std::string FromAbstractPathJson(const std::string &jsonAtomPath); + std::filesystem::path FromAbstractPathString(const std::string &stringPath); //std::vector GetPedalboards(); diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index af19e1c..8045bd6 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -520,7 +520,7 @@ public: res.set(HttpField::content_type, "application/json"); res.set(HttpField::cache_control, "no-cache"); - fs::path path = model->GetStorage().FromAbstractPathJson(request_uri.query("path")); + fs::path path = model->GetStorage().FromAbstractPathString(request_uri.query("path")); if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path)) { diff --git a/vite/src/pipedal/Lv2Plugin.tsx b/vite/src/pipedal/Lv2Plugin.tsx index e7d4b09..cdaca30 100644 --- a/vite/src/pipedal/Lv2Plugin.tsx +++ b/vite/src/pipedal/Lv2Plugin.tsx @@ -47,6 +47,7 @@ export class Port implements Deserializable { this.scale_points = ScalePoint.deserialize_array(input.scale_points); this.is_input = input.is_input; this.is_output = input.is_output; + this.is_sidechain = input.is_sidechain; this.is_control_port = input.is_control_port; this.is_audio_port = input.is_audio_port; this.is_atom_port = input.is_atom_port; @@ -80,6 +81,7 @@ export class Port implements Deserializable { is_output: boolean = false is_control_port: boolean = false; is_audio_port: boolean = false; + is_sidechain: boolean = false; is_atom_port: boolean = false; is_valid: boolean = false; supports_midi: boolean = false; @@ -92,9 +94,11 @@ export class Port implements Deserializable { export class PortGroup { deserialize(input: any): PortGroup { + this.isA = input.isA; this.uri = input.uri; this.symbol = input.symbol; this.name = input.name; + this.sideChainof = input.sideChainOf; this.parent_group = input.parent_group; this.program_list_id = input.program_list_id ?? -1; @@ -108,9 +112,12 @@ export class PortGroup { return result; } + isA: string[] = []; uri: string = ""; symbol: string = ""; name: string = ""; + sideChainof: string = ""; + parent_group: string = ""; program_list_id: number = -1; }; @@ -967,6 +974,8 @@ export class UiPlugin implements Deserializable { this.author_name = input.author_name; this.author_homepage = input.author_homepage; this.audio_inputs = input.audio_inputs; + this.audio_side_chain_inputs = input.audio_side_chain_inputs ?? 0; + this.audio_side_chain_title = input.audio_side_chain_title ?? ""; this.audio_outputs = input.audio_outputs; this.has_midi_input = input.has_midi_input; this.has_midi_output = input.has_midi_output; @@ -1057,7 +1066,9 @@ export class UiPlugin implements Deserializable { author_name: string = ""; author_homepage: string = ""; audio_inputs: number = 0; + audio_side_chain_inputs: number = 0; audio_outputs: number = 0; + audio_side_chain_title: string = ""; has_midi_input: number = 0; has_midi_output: number = 0; description: string = ""; @@ -1084,6 +1095,7 @@ export function makeSplitUiPlugin(): UiPlugin { author_name: "", author_homepage: "", audio_inputs: 1, + audio_side_chain_inputs: 0, audio_outputs: 1, has_midi_input: 0, has_midi_output: 0, diff --git a/vite/src/pipedal/Pedalboard.tsx b/vite/src/pipedal/Pedalboard.tsx index 157a5e4..56b9d94 100644 --- a/vite/src/pipedal/Pedalboard.tsx +++ b/vite/src/pipedal/Pedalboard.tsx @@ -81,6 +81,7 @@ export class PedalboardItem implements Deserializable { this.pathProperties = input.pathProperties; this.useModUi = input.useModUi ?? false; this.iconColor = input.iconColor??""; + this.sideChainInputId = input.sideChainInputId ?? -1; return this; } @@ -217,6 +218,7 @@ export class PedalboardItem implements Deserializable { pathProperties: {[Name: string]: string} = {}; useModUi: boolean = false; // true if this item should use the mod-ui. iconColor: string = ""; + sideChainInputId: number = -1; // -1 means no sidechain input. }; export class SnapshotValue { diff --git a/vite/src/pipedal/PiPedalModel.tsx b/vite/src/pipedal/PiPedalModel.tsx index 88c2882..b2c478f 100644 --- a/vite/src/pipedal/PiPedalModel.tsx +++ b/vite/src/pipedal/PiPedalModel.tsx @@ -550,19 +550,16 @@ export class PiPedalModel //implements PiPedalModel ); zoomedUiControl: ObservableProperty = new ObservableProperty(undefined); + uiPluginsByUri: Map = new Map(); + svgImgUrl(svgImage: string): string { - //return this.varServerUrl + "img/" + svgImage; + // return this.varServerUrl + "img/" + svgImage; return "img/" + svgImage; } getUiPlugin(uri: string): UiPlugin | null { - let plugins: UiPlugin[] = this.ui_plugins.get(); - - for (let plugin of plugins) { - if (plugin.uri === uri) return plugin; - } - return null; + return this.uiPluginsByUri.get(uri) ?? null; } androidHost?: AndroidHostInterface; @@ -1210,6 +1207,11 @@ export class PiPedalModel //implements PiPedalModel this.ui_plugins.set( UiPlugin.deserialize_array(await this.getWebSocket().request("plugins")) ); + // index ui plugins. + this.uiPluginsByUri = new Map(); + for (let i of this.ui_plugins.get()) { + this.uiPluginsByUri.set(i.uri, i); + } this.setModelPedalboard( new Pedalboard().deserialize( await this.getWebSocket().request("currentPedalboard") @@ -3552,6 +3554,20 @@ export class PiPedalModel //implements PiPedalModel copyPresetsToBank(bankInstanceId: number, presets: number[]): Promise { return nullCast(this.webSocket).request("copyPresetsToBank", {bankInstanceId: bankInstanceId, presets: presets}); } + setPedalboardSideChainInput(instanceId: number, sideChainInputId: number): void { + let pedalboard = this.pedalboard.get().clone(); + let pedalboardItem = pedalboard.getItem(instanceId); + if (!pedalboardItem) + { + return; + } + pedalboardItem.sideChainInputId = sideChainInputId; + pedalboardItem.instanceId = ++pedalboard.nextInstanceId; + pedalboard.selectedPlugin = pedalboardItem.instanceId; + this.setModelPedalboard(pedalboard); + this.updateServerPedalboard(); + + } }; let instance: PiPedalModel | undefined = undefined; diff --git a/vite/src/pipedal/PluginControlView.tsx b/vite/src/pipedal/PluginControlView.tsx index 66ef9fb..7dbd7ce 100644 --- a/vite/src/pipedal/PluginControlView.tsx +++ b/vite/src/pipedal/PluginControlView.tsx @@ -37,6 +37,7 @@ import { } from './Pedalboard'; import PluginControl from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; +import SideChainSelectControl from './SideChainSelectControl'; import VuMeter from './VuMeter'; import { nullCast } from './Utility' import { PiPedalStateError } from './PiPedalError'; @@ -66,6 +67,11 @@ export interface ICustomizationHost { isLandscapeGrid(): boolean; } +interface SideChainSelectItem { + instanceId: number; + title: string; +} + function makeIoPluginInfo(name: string, uri: string): UiPlugin { let result = new UiPlugin(); result.name = name; @@ -371,7 +377,7 @@ type PluginControlViewState = { const PluginControlView = withTheme(withStyles( - class extends ResizeResponsiveComponent implements ICustomizationHost { + class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: PluginControlViewProps) { @@ -553,6 +559,68 @@ const PluginControlView = } } + private getSidechainSelectItems(): SideChainSelectItem[] { + let myInstanceId = this.props.item.instanceId; + + let items: SideChainSelectItem[] = []; + items.push({ instanceId: -1, title: "None" }); + items.push({ instanceId: -2, title: "Input" }); + + let pedalboard = this.model.pedalboard.get(); + if (!pedalboard) return items; + + let it = pedalboard.itemsGenerator(); + + let found = false; + while (true) { + let v = it.next(); + if (v.done) { + break; + } + let pedalboardItem = v.value; + if (pedalboardItem.isSplit()) { + continue; + } + if (pedalboardItem.instanceId === myInstanceId) { + found = true; + break; + } + if (pedalboardItem.uri.length === 0) { + continue; + } + let pluginInfo = this.model.getUiPlugin(pedalboardItem.uri); + if (!pluginInfo) continue; + let name = pluginInfo.name; + if (pedalboardItem.title.length !== 0) { + name = pedalboardItem.title; + } + items.push({ instanceId: pedalboardItem.instanceId, title: name }); + } + if (!found) { + return [{ instanceId: -1, title: "None" }]; + } + return items; + } + + private makeSideChainSelect(uiPlugin: UiPlugin) { + let items = this.getSidechainSelectItems(); + let selectedInstanceId = this.props.item.sideChainInputId; + let title = uiPlugin.audio_side_chain_title ? uiPlugin.audio_side_chain_title : "Side chain"; + + return ( + { + this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); + + }} + /> + ); + + } + isLandscapeGrid(): boolean { return this.state.landscapeGrid; } @@ -575,7 +643,7 @@ const PluginControlView = } return (
{controlGroup.name !== "" && (
@@ -711,6 +779,11 @@ const PluginControlView = ); } } + if (plugin.audio_side_chain_inputs !== 0) { + + result.push(this.makeSideChainSelect(plugin)); + + } return result; } diff --git a/vite/src/pipedal/PluginOutputControl.tsx b/vite/src/pipedal/PluginOutputControl.tsx index e5a681b..67a8027 100644 --- a/vite/src/pipedal/PluginOutputControl.tsx +++ b/vite/src/pipedal/PluginOutputControl.tsx @@ -20,7 +20,7 @@ import React, { Component } from 'react'; import { Theme } from '@mui/material/styles'; import WithStyles from './WithStyles'; -import {createStyles} from './WithStyles'; +import { createStyles } from './WithStyles'; import { withStyles } from "tss-react/mui"; import { UiControl } from './Lv2Plugin'; @@ -47,8 +47,7 @@ const defaultVuColors: VuColor[] = [ function getVuColor(vuColors: VuColor[], levelDb: number): string { let ix = 0; - for (let i = 0; i < vuColors.length; ++i) - { + for (let i = 0; i < vuColors.length; ++i) { let c = vuColors[i]; if (levelDb > c.minDb && levelDb <= c.maxDb) { ix = i; @@ -122,18 +121,18 @@ const styles = (theme: Theme) => createStyles({ }), titleSection: css({ - flex: "0 0 auto", alignSelf:"stretch", marginBottom: 8, marginLeft: 0, marginRight: 0 + flex: "0 0 auto", alignSelf: "stretch", marginBottom: 8, marginLeft: 0, marginRight: 0 }), midSection: css({ - flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center" + flex: "1 1 1", display: "flex", flexFlow: "column nowrap", alignContent: "center", justifyContent: "center" }), editSection: css({ flex: "0 0 28", position: "relative", width: 60, height: 28, minHeight: 28 }), editSectionNoContent: css({ - flex: "0 0 28", position: "relative", width: 1, height: 28, minHeight: 28 + flex: "0 0 28", position: "relative", width: 1, height: 28, minHeight: 28 }) }); @@ -153,12 +152,12 @@ const PluginOutputControl = class extends Component { private model: PiPedalModel; - private vuRef: React.RefObject; - private progressRef: React.RefObject; - private dbVuRef: React.RefObject; - private dbVuTelltaleRef: React.RefObject; - private dbVuTextRef: React.RefObject; - private lampRef: React.RefObject; + private vuRef: React.RefObject; + private progressRef: React.RefObject; + private dbVuRef: React.RefObject; + private dbVuTelltaleRef: React.RefObject; + private dbVuTextRef: React.RefObject; + private lampRef: React.RefObject; constructor(props: PluginOutputControlProps) { @@ -210,7 +209,7 @@ const PluginOutputControl = } } - private PROGRESS_WIDTH = 40-4; + private PROGRESS_WIDTH = 40 - 4; private VU_HEIGHT = 60 - 4; private DB_VU_HEIGHT = 60 - 4; private animationHandle: number | undefined = undefined; @@ -240,26 +239,23 @@ const PluginOutputControl = } if (this.dbVuTextRef.current) { let text: string; - if (this.dbVuTelltale <= this.props.uiControl.min_value) - { + if (this.dbVuTelltale <= this.props.uiControl.min_value) { text = "-"; } else { - if (Math.abs(this.dbVuTelltale) >= 99.5) - { + if (Math.abs(this.dbVuTelltale) >= 99.5) { text = Math.round(this.dbVuTelltale).toString(); } else { text = this.dbVuTelltale.toFixed(1); } if (!text.startsWith("-")) { - text = '+'+text; - } + text = '+' + text; + } } - if (this.lastDbText !== text) - { + if (this.lastDbText !== text) { this.lastDbText = text; this.dbVuTextRef.current.innerText = text; } - } + } this.animationHandle = undefined; this.updateDbVuTelltale(); } @@ -322,7 +318,7 @@ const PluginOutputControl = ) } - } + } else if (this.vuRef.current) { let control = this.props.uiControl; let range = (value - control.min_value) / (control.max_value - control.min_value); @@ -389,8 +385,7 @@ const PluginOutputControl = if (value > control.max_value) { value = control.max_value; } - if (value < control.min_value) - { + if (value < control.min_value) { value = control.min_value; } let y = (control.max_value - value) * this.DB_VU_HEIGHT / (control.max_value - control.min_value); @@ -439,13 +434,13 @@ const PluginOutputControl = } else if (control.isProgress()) { item_width = undefined; return ( -
{/* TITLE SECTION */} -
+
- {control.name === "" ? "\u00A0" : control.name} @@ -453,15 +448,18 @@ const PluginOutputControl =
{/* CONTROL SECTION */} -
-
-
-
-
+
+
+
+ }} />
@@ -478,45 +476,48 @@ const PluginOutputControl = let vuColors = defaultVuColors; return ( -
{/* TITLE SECTION */} -
- - {control.name === "" ? "\u00A0" : control.name} - +
+ {control.name === "" ? "\u00A0" : control.name}
{/* CONTROL SECTION */} -
-
-
-
- { - vuColors.map((vuColor,ix)=>{ - let top = Math.floor(this.dbVuMap(vuColor.maxDb)); - let bottom = Math.ceil(this.dbVuMap(vuColor.minDb))+1; + - return ( -
- ); - }) - } +
+
+
+ { + vuColors.map((vuColor, ix) => { + let top = Math.floor(this.dbVuMap(vuColor.maxDb)); + let bottom = Math.ceil(this.dbVuMap(vuColor.minDb)) + 1; + + return ( +
+ ); + }) + } +
+ +
+
- -
-
-
+
@@ -533,26 +534,28 @@ const PluginOutputControl = else if (control.isVu()) { item_width = undefined; return ( -
+
{/* TITLE SECTION */} -
- - {control.name === "" ? "\u00A0" : control.name} - + + {control.name === "" ? "\u00A0" : control.name} +
{/* CONTROL SECTION */}
-
-
-
+ style={{ display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}> + +
+
+
+
-
+
@@ -575,11 +578,11 @@ const PluginOutputControl = } return (
+ style={{ width: attachedLamp ? 15 : item_width }} + > {/* TITLE SECTION */} -
+
{/* CONTROL SECTION */} -
@@ -613,23 +616,23 @@ const PluginOutputControl = ); } if (control.isOutputText()) { return ( -
+
{/* TITLE SECTION */} -
{control.name === "" ? "\u00A0": control.name} + }}> {control.name === "" ? "\u00A0" : control.name}
{/* CONTROL SECTION */}
@@ -646,10 +649,10 @@ const PluginOutputControl = } else { return ( -
+
{/* TITLE SECTION */} -
{/* CONTROL SECTION */} -
{text} diff --git a/vite/src/pipedal/SideChainSelectControl.tsx b/vite/src/pipedal/SideChainSelectControl.tsx new file mode 100644 index 0000000..b647b81 --- /dev/null +++ b/vite/src/pipedal/SideChainSelectControl.tsx @@ -0,0 +1,166 @@ +/* + * MIT License + * + * Copyright (c) Robin E. R. 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. + */ + +import { Component } from 'react'; +import { Theme } from '@mui/material/styles'; +import { css } from '@emotion/react'; + +import Typography from '@mui/material/Typography'; +import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; + + +export const StandardItemSize = { width: 80, height: 140 } + + +import { withStyles } from "tss-react/mui"; +import WithStyles from './WithStyles'; +import { withTheme } from './WithStyles'; + +const styles = (theme: Theme) => { + return { + frame: css({ + position: "relative", + margin: "12px" + }), + controlFrame: css({ + display: "flex", flexDirection: "column", alignItems: "start", justifyContent: "space-between", height: 116 + }), + displayValue: css({ + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 4, + textAlign: "center", + background: theme.mainBackground, + color: theme.palette.text.secondary, + // zIndex: -1, + }), + titleSection: css({ + flex: "0 0 auto", alignSelf: "stretch", marginBottom: 8, marginLeft: 8, marginRight: 0 + }), + midSection: css({ + flex: "1 1 1", display: "flex", flexFlow: "column nowrap", alignContent: "center", justifyContent: "center" + }), + editSection: css({ + flex: "0 0 0", position: "relative", width: 60, height: 28, minHeight: 28 + }) + } +} + ; + + +export interface SideChainSelectControlProps extends WithStyles { + title: string; + selectItems: { instanceId: number, title: string }[]; + selectedInstanceId: number; + onChanged(instanceId: number): void; + theme: Theme; +} +type SideChainSelectControlState = { +}; + +const SideChainSelectControl = + withTheme(withStyles( + class extends Component { + + model: PiPedalModel; + + + constructor(props: SideChainSelectControlProps) { + super(props); + + this.state = { + }; + this.model = PiPedalModelFactory.getInstance(); + this.onStateChanged = this.onStateChanged.bind(this); + } + + + onStateChanged(state: State) { + } + mounted: boolean = false; + componentDidMount() { + this.model.state.addOnChangedHandler(this.onStateChanged); + this.mounted = true; + } + componentWillUnmount() { + this.mounted = false; + this.model.state.removeOnChangedHandler(this.onStateChanged); + } + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + } + + render() { + //const classes = withStyles.getClasses(this.props); + + const classes = withStyles.getClasses(this.props); + + return ( +
+ {/* TITLE SECTION */} +
+ {this.props.title} +
+ {/* CONTROL SECTION */} + +
+ + +
+ + {/* LABEL/EDIT SECTION*/} +
+
+
+ ); + } + } + , styles) + ) + ; +export default SideChainSelectControl; \ No newline at end of file