// 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 "Lv2PedalBoard.hpp" #include "Lv2Effect.hpp" #include "SplitEffect.hpp" #include "RingBufferReader.hpp" #include "VuUpdate.hpp" #include "JackHost.hpp" #include "Lv2EventBufferWriter.hpp" using namespace pipedal; float *Lv2PedalBoard::CreateNewAudioBuffer() { return bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize()); } std::vector Lv2PedalBoard::AllocateAudioBuffers(int nChannels) { std::vector result; for (int i = 0; i < nChannels; ++i) { result.push_back(bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize())); } return result; } int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbol) { for (int i = 0; i < realtimeEffects.size(); ++i) { auto item = realtimeEffects[i]; if (item->GetInstanceId() == instanceId) { return item->GetControlIndex(symbol); } } return -1; } std::vector Lv2PedalBoard::PrepareItems( const std::vector &items, std::vector inputBuffers) { for (int i = 0; i < items.size(); ++i) { auto &item = items[i]; if (!item.isEmpty()) { IEffect *pEffect = nullptr; if (item.isSplit()) { auto pSplit = new SplitEffect(item.instanceId(), pHost->GetSampleRate(), inputBuffers); pEffect = pSplit; int topInputChannels = inputBuffers.size(); int bottomInputChannels = inputBuffers.size(); std::vector topInputs = AllocateAudioBuffers(topInputChannels); std::vector bottomInputs = AllocateAudioBuffers(bottomInputChannels); auto preMixAction = [pSplit](uint32_t frames) { pSplit->PreMix(frames); }; this->processActions.push_back(preMixAction); std::vector topResult = PrepareItems(item.topChain(), topInputs); std::vector bottomResult = PrepareItems(item.bottomChain(), bottomInputs); this->processActions.push_back( [pSplit](uint32_t frames) { pSplit->PostMix(frames); }); pSplit->SetChainBuffers(topInputs, bottomInputs, topResult, bottomResult); for (int i = 0; i < item.controlValues().size(); ++i) { auto &controlValue = item.controlValues()[i]; int index = pSplit->GetControlIndex(controlValue.key()); if (index != -1) { pSplit->SetControl(index, controlValue.value()); } } } else { auto pLv2Effect = this->pHost->CreateEffect(item); if (pLv2Effect) { pEffect = pLv2Effect; uint64_t instanceId = pEffect->GetInstanceId(); if (inputBuffers.size() == 1) { if (pLv2Effect->GetNumberOfInputAudioPorts() == 1) { pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]); } else { pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]); pLv2Effect->SetAudioInputBuffer(1, inputBuffers[0]); } } else { if (pLv2Effect->GetNumberOfInputAudioPorts() == 1) { pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]); auto inputBuffer = inputBuffers[0]; } else { pLv2Effect->SetAudioInputBuffer(0, inputBuffers[0]); pLv2Effect->SetAudioInputBuffer(1, inputBuffers[1]); auto bufferL = inputBuffers[0]; auto bufferR = inputBuffers[1]; } } this->processActions.push_back( [pLv2Effect,this,instanceId](uint32_t frames) { pLv2Effect->Run(frames,instanceId,this->ringBufferWriter); }); } } if (pEffect) { this->effects.push_back(std::shared_ptr(pEffect)); // for ownership. this->realtimeEffects.push_back(pEffect); // because std::shared_ptr is not threadsafe. std::vector effectOutput; #ifdef RECYCLE_AUDIO_BUFFERS // can't do this anymore if we're going to do pop-less bypbass. if (pEffect->GetNumberOfOutputAudioPorts() == 1) { float *pLeft = inputBuffers[0]; effectOutput.push_back(pLeft); } else { if (inputBuffers.size() == 1) { effectOutput.push_back(inputBuffers[0]); effectOutput.push_back(CreateNewAudioBuffer()); } else { effectOutput.push_back(inputBuffers[0]); effectOutput.push_back(inputBuffers[1]); } } #else if (pEffect->GetNumberOfOutputAudioPorts() == 1) { effectOutput.push_back(CreateNewAudioBuffer()); } else { effectOutput.push_back(CreateNewAudioBuffer()); effectOutput.push_back(CreateNewAudioBuffer()); } #endif for (size_t i = 0; i < effectOutput.size(); ++i) { pEffect->SetAudioOutputBuffer(i, effectOutput[i]); } inputBuffers = effectOutput; } } } return inputBuffers; } void Lv2PedalBoard::Prepare(IHost *pHost, const PedalBoard &pedalBoard) { this->pHost = pHost; for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i) { this->pedalBoardInputBuffers.push_back(bufferPool.AllocateBuffer(pHost->GetMaxAudioBufferSize())); } auto outputs = PrepareItems(pedalBoard.items(), this->pedalBoardInputBuffers); int nOutputs = pHost->GetNumberOfOutputAudioChannels(); if (nOutputs == 1) { this->pedalBoardOutputBuffers.push_back(outputs[0]); } else { if (outputs.size() == 1) { this->pedalBoardOutputBuffers.push_back(outputs[0]); this->pedalBoardOutputBuffers.push_back(outputs[0]); } else { this->pedalBoardOutputBuffers.push_back(outputs[0]); this->pedalBoardOutputBuffers.push_back(outputs[1]); } } PrepareMidiMap(pedalBoard); } void Lv2PedalBoard::PrepareMidiMap(const PedalBoardItem &pedalBoardItem) { if (pedalBoardItem.midiBindings().size() != 0) { auto pluginInfo = pHost->GetPluginInfo(pedalBoardItem.uri()); const Lv2PluginInfo *pPluginInfo; if (pluginInfo == nullptr && pedalBoardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPluginInfo = GetSplitterPluginInfo(); } else { pPluginInfo = pluginInfo.get(); } int effectIndex = this->GetIndexOfInstanceId(pedalBoardItem.instanceId()); if (pluginInfo && effectIndex != -1) { for (size_t bindingIndex = 0; bindingIndex < pedalBoardItem.midiBindings().size(); ++bindingIndex) { auto &binding = pedalBoardItem.midiBindings()[bindingIndex]; { const Lv2PortInfo*pPortInfo; int controlIndex; if (binding.symbol() == "__bypass") { pPortInfo = GetBypassPortInfo(); controlIndex = -1; } else { try { pPortInfo = &pluginInfo->getPort(binding.symbol()); controlIndex = this->GetControlIndex(pedalBoardItem.instanceId(), binding.symbol()); } catch (const std::exception&ignored) { continue; } } MidiMapping mapping; mapping.pluginInfo = pluginInfo; // for lifetime management. We're holding internal pointers to this. May save us in a disorderly shutdown. mapping.pPortInfo = pPortInfo; mapping.effectIndex = effectIndex; mapping.controlIndex = controlIndex; mapping.midiBinding = binding; mapping.instanceId = pedalBoardItem.instanceId(); if (pPortInfo->IsSwitch()) { mapping.mappingType = binding.switchControlType() == LATCH_CONTROL_TYPE ? MappingType::Latched : MappingType::Momentary; } else { mapping.mappingType = binding.linearControlType() == LATCH_CONTROL_TYPE ? MappingType::Linear : MappingType::Circular; } if (binding.bindingType() == BINDING_TYPE_NOTE) { mapping.key = 0x9000 | binding.note(); // i.e. midi note on. } else if (binding.bindingType() == BINDING_TYPE_CONTROL) { mapping.key = 0xB000 | binding.control(); // i.e. midi control } else { mapping.key = -1; } if (mapping.key != -1) { midiMappings.push_back(std::move(mapping)); } } } } } for (size_t i = 0; i < pedalBoardItem.topChain().size(); ++i) { PrepareMidiMap(pedalBoardItem.topChain()[i]); } for (size_t i = 0; i < pedalBoardItem.bottomChain().size(); ++i) { PrepareMidiMap(pedalBoardItem.bottomChain()[i]); } } void Lv2PedalBoard::PrepareMidiMap(const PedalBoard &pedalBoard) { for (size_t i = 0; i < pedalBoard.items().size(); ++i) { auto &item = pedalBoard.items()[i]; PrepareMidiMap(item); auto pluginInfo = pHost->GetPluginInfo(item.uri()); if (pluginInfo) { for (size_t bindingIndex = 0; bindingIndex < item.midiBindings().size(); ++bindingIndex) { auto &binding = item.midiBindings()[i]; { } } } std::sort(this->midiMappings.begin(), this->midiMappings.end(), [](const MidiMapping &left, const MidiMapping &right) { return left.key < right.key; }); } } void Lv2PedalBoard::Activate() { for (int i = 0; i < this->effects.size(); ++i) { this->realtimeEffects[i]->Activate(); } } void Lv2PedalBoard::Deactivate() { for (int i = 0; i < this->effects.size(); ++i) { this->realtimeEffects[i]->Deactivate(); } } static void Copy(float *input, float *output, uint32_t samples) { for (uint32_t i = 0; i < samples; ++i) { output[i] = input[i]; } } bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter) { this->ringBufferWriter = ringBufferWriter; for (int i = 0; i < this->pedalBoardInputBuffers.size(); ++i) { if (inputBuffers[i] == nullptr) return false; Copy(inputBuffers[i], this->pedalBoardInputBuffers[i], samples); } for (int i = 0; i < this->processActions.size(); ++i) { processActions[i](samples); } for (int i = 0; i < this->pedalBoardOutputBuffers.size(); ++i) { if (outputBuffers[i] == nullptr) return false; Copy(this->pedalBoardOutputBuffers[i], outputBuffers[i], samples); } return true; } float Lv2PedalBoard::GetControlOutputValue(int effectIndex, int portIndex) { auto effect = realtimeEffects[effectIndex]; return effect->GetOutputControlValue(portIndex); } void Lv2PedalBoard::SetControlValue(int effectIndex, int index, float value) { auto effect = realtimeEffects[effectIndex]; effect->SetControl(index, value); } void Lv2PedalBoard::SetBypass(int effectIndex, bool enabled) { auto effect = realtimeEffects[effectIndex]; effect->SetBypass(enabled); } void Lv2PedalBoard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples) { for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i) { int index = vuConfiguration->enabledIndexes[i]; VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i]; auto effect = this->realtimeEffects[index]; if (effect->GetNumberOfInputAudioPorts() == 1) { pUpdate->AccumulateInputs(effect->GetAudioInputBuffer(0), samples); } else { pUpdate->AccumulateInputs( effect->GetAudioInputBuffer(0), effect->GetAudioInputBuffer(1), samples); } if (effect->GetNumberOfOutputAudioPorts() == 1) { pUpdate->AccumulateOutputs(effect->GetAudioOutputBuffer(0), samples); } else { pUpdate->AccumulateOutputs( effect->GetAudioOutputBuffer(0), effect->GetAudioOutputBuffer(1), samples); } } } void Lv2PedalBoard::ResetAtomBuffers() { for (size_t i = 0; i < this->effects.size(); ++i) { auto effect = this->effects[i]; effect->ResetAtomBuffers(); } } void Lv2PedalBoard::ProcessParameterRequests(RealtimeParameterRequest *pParameterRequests) { while (pParameterRequests != nullptr) { IEffect *pEffect = this->GetEffect(pParameterRequests->instanceId); if (pEffect == nullptr) { pParameterRequests->errorMessage = "No such effect."; } else { pEffect->RequestParameter(pParameterRequests->uridUri); } pParameterRequests = pParameterRequests->pNext; } } void Lv2PedalBoard::GatherParameterRequests(RealtimeParameterRequest *pParameterRequests) { while (pParameterRequests != nullptr) { IEffect *effect = this->GetEffect(pParameterRequests->instanceId); if (effect == nullptr) { pParameterRequests->errorMessage = "No such effect."; } else { effect->GatherParameter(pParameterRequests); } pParameterRequests = pParameterRequests->pNext; } } void Lv2PedalBoard::OnMidiMessage(size_t size, uint8_t *message, void *callbackHandle, MidiCallbackFn *pfnCallback) { if (midiMappings.size() == 0) return; if (size < 2) return; uint8_t cmd = message[0]; uint8_t channel = cmd & 0x0F; cmd &= 0xF0; uint8_t value; uint8_t index; if (cmd == 0x80) // note off. { index = message[1]; cmd = 0x90; index = message[1]; value = 0; } else if (cmd == 0x90) // note on. { if (size < 3) return; index = message[1]; value = 127; } else if (cmd == 0xB0) // midi control. { if (size < 3) return; index = message[1]; value = message[2] & 0x7F; } int searchKey = (cmd << 8) | index; int min = 0; int max = midiMappings.size() - 1; while (max > min) { int mid = (min + max) / 2; if (midiMappings[mid].key < searchKey) { min = mid + 1; } else if (midiMappings[mid].key > searchKey) { max = mid - 1; } else { if (mid == 0) { min = max = mid; } else { if (midiMappings[mid - 1].key == searchKey) { max = mid - 1; } else { min = max = mid; } } } } if (midiMappings[min].key == searchKey) { float range = value / 127.0; for (int i = min; i < midiMappings.size(); ++i) { auto &mapping = midiMappings[i]; if (mapping.key != searchKey) break; if (mapping.midiBinding.channel() == -1 || mapping.midiBinding.channel() == channel) { switch (mapping.mappingType) { case MappingType::Circular: case MappingType::Linear: { float thisRange = (mapping.midiBinding.maxValue() - mapping.midiBinding.minValue()) * range + mapping.midiBinding.minValue(); float value = mapping.pPortInfo->rangeToValue(thisRange); this->SetControlValue(mapping.effectIndex, mapping.controlIndex, value); pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), value); break; } case MappingType::Latched: { range = std::round(range); if (!mapping.hasLastValue) { mapping.lastValue = 0; mapping.hasLastValue = true; } if (range != mapping.lastValue && range == 1) { IEffect *pEffect = this->realtimeEffects[mapping.effectIndex]; float currentValue = pEffect->GetControlValue(mapping.controlIndex); currentValue = currentValue == 0 ? 1 : 0; pEffect->SetControl(mapping.controlIndex, currentValue); pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), currentValue); } mapping.lastValue = range; break; } case MappingType::Momentary: { IEffect *pEffect = this->realtimeEffects[mapping.effectIndex]; float value = mapping.pPortInfo->rangeToValue(range); if (pEffect->GetControlValue(mapping.controlIndex) != value) { this->SetControlValue(mapping.effectIndex, mapping.controlIndex, value); pfnCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), value); } break; } } } } } }