// 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 #include "PiPedalModel.hpp" #include "JackHost.hpp" #include "Lv2Log.hpp" #include #include "PiPedalConfiguration.hpp" #include "ShutdownClient.hpp" #include "SplitEffect.hpp" #ifndef NO_MLOCK #include #endif /* NO_MLOCK */ using namespace pipedal; template T &constMutex(const T &mutex) { return const_cast(mutex); } std::string AtomToJson(int length, uint8_t *pData) { return ""; } PiPedalModel::PiPedalModel() { this->pedalBoard = PedalBoard::MakeDefault(); this->jackServerSettings.ReadJackConfiguration(); } void PiPedalModel::Close() { std::lock_guard guard(mutex); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->Close(); } delete[] t; if (jackHost) { jackHost->Close(); } } PiPedalModel::~PiPedalModel() { CurrentPreset currentPreset; currentPreset.modified_ = this->hasPresetChanged; currentPreset.preset_ = this->pedalBoard; storage.SaveCurrentPreset(currentPreset); if (jackHost) { jackHost->Close(); } } #include void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration) { storage.SetDataRoot(configuration.GetLocalStoragePath().c_str()); storage.Initialize(); // Not all Lv2 directories have the lv2 base declarations. Load a full set of plugin classes generated on a default /usr/local/lib/lv2 directory. std::filesystem::path pluginClassesPath = configuration.GetDocRoot() / "plugin_classes.json"; try { if (!std::filesystem::exists(pluginClassesPath)) throw PiPedalException("File not found."); lv2Host.LoadPluginClassesFromJson(pluginClassesPath); } catch (const std::exception &e) { std::stringstream s; s << "Unable to load " << pluginClassesPath << ". " << e.what(); throw PiPedalException(s.str().c_str()); } lv2Host.Load(configuration.GetLv2Path().c_str()); // Copy all presets out of Lilv data to json files // so that we can close lilv while we're actually // running. for (const auto&plugin: lv2Host.GetPlugins()) { if (plugin->has_factory_presets()) { if (!storage.HasPluginPresets(plugin->uri())) { PluginPresets pluginPresets = lv2Host.GetFactoryPluginPresets(plugin->uri()); storage.SavePluginPresets(plugin->uri(),pluginPresets); } } } } void PiPedalModel::Load(const PiPedalConfiguration &configuration) { this->webRoot = configuration.GetWebRoot(); this->jackServerSettings.ReadJackConfiguration(); // lv2Host.Load(configuration.GetLv2Path().c_str()); this->pedalBoard = storage.GetCurrentPreset(); // the current *saved* preset. // the current edited preset, saved only across orderly shutdowns. CurrentPreset currentPreset; if (storage.RestoreCurrentPreset(¤tPreset)) { this->pedalBoard = currentPreset.preset_; this->hasPresetChanged = currentPreset.modified_; } UpdateDefaults(&this->pedalBoard); std::unique_ptr p{JackHost::CreateInstance(lv2Host.asIHost())}; this->jackHost = std::move(p); this->jackHost->SetNotificationCallbacks(this); if (configuration.GetMLock()) { #ifndef NO_MLOCK int result = mlockall(MCL_CURRENT | MCL_FUTURE); if (result) { throw PiPedalStateException("mlockall failed. You can disable the call to mlockall in 'config.json'."); } #endif } struct sched_param scheduler_params; scheduler_params.sched_priority = 10; memset(&scheduler_params,0,sizeof(sched_param)); sched_setscheduler(0,SCHED_RR,&scheduler_params); this->jackConfiguration = jackHost->GetServerConfiguration(); if (this->jackConfiguration.isValid()) { JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); selection = selection.RemoveInvalidChannels(jackConfiguration); this->lv2Host.OnConfigurationChanged(jackConfiguration, selection); try { jackHost->Open(selection); std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; this->lv2PedalBoard = lv2PedalBoard; jackHost->SetPedalBoard(lv2PedalBoard); } catch (PiPedalException &e) { Lv2Log::error("Failed to load initial plugin. (%s)", e.what()); } } } IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientId) { for (size_t i = 0; i < subscribers.size(); ++i) { if (subscribers[i]->GetClientId() == clientId) { return subscribers[i]; } } return nullptr; } void PiPedalModel::AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber) { std::lock_guard guard(mutex); this->subscribers.push_back(pSubscriber); } void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber) { { std::lock_guard guard(mutex); for (auto it = this->subscribers.begin(); it != this->subscribers.end(); ++it) { if ((*it) == pSubscriber) { this->subscribers.erase(it); break; } } int64_t clientId = pSubscriber->GetClientId(); this->DeleteMidiListeners(clientId); this->DeleteAtomOutputListeners(clientId); for (int i = 0; i < this->outstandingParameterRequests.size(); ++i) { if (outstandingParameterRequests[i]->clientId == clientId) { outstandingParameterRequests.erase(outstandingParameterRequests.begin() + i); --i; } } } } void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { jackHost->SetControlValue(pedalItemId, symbol, value); } void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { { std::lock_guard guard(mutex); PreviewControl(clientId, pedalItemId, symbol, value); this->pedalBoard.SetControlValue(pedalItemId, symbol, value); { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnControlChanged(clientId, pedalItemId, symbol, value); } delete[] t; this->SetPresetChanged(clientId, true); } } } void PiPedalModel::FireJackConfigurationChanged(const JackConfiguration &jackConfiguration) { // noify subscribers. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnJackConfigurationChanged(jackConfiguration); } delete[] t; } void PiPedalModel::FireBanksChanged(int64_t clientId) { // noify subscribers. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnBankIndexChanged(this->storage.GetBanks()); } delete[] t; } void PiPedalModel::FirePedalBoardChanged(int64_t clientId) { // noify subscribers. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnPedalBoardChanged(clientId, this->pedalBoard); } delete[] t; // notify the audio thread. if (jackHost->IsOpen()) { std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; this->lv2PedalBoard = lv2PedalBoard; jackHost->SetPedalBoard(lv2PedalBoard); UpdateRealtimeVuSubscriptions(); UpdateRealtimeMonitorPortSubscriptions(); } } void PiPedalModel::SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard) { { std::lock_guard guard(mutex); this->pedalBoard = pedalBoard; UpdateDefaults(&this->pedalBoard); this->FirePedalBoardChanged(clientId); this->SetPresetChanged(clientId, true); } } void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled) { std::lock_guard guard{mutex}; { this->pedalBoard.SetItemEnabled(pedalItemId, enabled); // Notify clients. IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnItemEnabledChanged(clientId, pedalItemId, enabled); } delete[] t; this->SetPresetChanged(clientId, true); // Notify audo thread. this->jackHost->SetBypass(pedalItemId, enabled); } } void PiPedalModel::GetPresets(PresetIndex *pResult) { std::lock_guard guard(mutex); this->storage.GetPresetIndex(pResult); pResult->presetChanged(this->hasPresetChanged); } PedalBoard PiPedalModel::GetPreset(int64_t instanceId) { std::lock_guard guard(mutex); return this->storage.GetPreset(instanceId); } void PiPedalModel::GetBank(int64_t instanceId, BankFile *pResult) { std::lock_guard guard(mutex); this->storage.GetBankFile(instanceId, pResult); } void PiPedalModel::SetPresetChanged(int64_t clientId, bool value) { if (value != this->hasPresetChanged) { hasPresetChanged = value; FirePresetsChanged(clientId); } } void PiPedalModel::FirePresetsChanged(int64_t clientId) { std::lock_guard guard{mutex}; { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); PresetIndex presets; GetPresets(&presets); for (size_t i = 0; i < n; ++i) { t[i]->OnPresetsChanged(clientId, presets); } delete[] t; } } void PiPedalModel::FirePluginPresetsChanged(const std::string & pluginUri) { std::lock_guard guard{mutex}; { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnPluginPresetsChanged(pluginUri); } delete[] t; } } void PiPedalModel::SaveCurrentPreset(int64_t clientId) { std::lock_guard guard{mutex}; storage.SaveCurrentPreset(this->pedalBoard); this->SetPresetChanged(clientId, false); } uint64_t PiPedalModel::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId) { uint64_t result = storage.CopyPluginPreset(pluginUri,presetId); FirePluginPresetsChanged(pluginUri); return result; } void PiPedalModel::UpdatePluginPresets(const PluginUiPresets&pluginPresets) { storage.UpdatePluginPresets(pluginPresets); FirePluginPresetsChanged(pluginPresets.pluginUri_); } int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string&name) { auto *item = this->pedalBoard.GetItem(instanceId); if (!item) { throw PiPedalException("Plugin not found."); } std::map values; for (const auto & controlValue: item->controlValues()) { values[controlValue.key()] = controlValue.value(); } uint64_t presetId = storage.SavePluginPreset(item->uri(),name,values); FirePluginPresetsChanged(item->uri()); return presetId; } int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId) { std::lock_guard guard{mutex}; int64_t result = storage.saveCurrentPresetAs(this->pedalBoard, name, saveAfterInstanceId); this->hasPresetChanged = false; FirePresetsChanged(clientId); return result; } void PiPedalModel::UploadPluginPresets(const PluginPresets&pluginPresets) { if (pluginPresets.pluginUri_.length() == 0) { throw PiPedalException("Invalid plugin presets."); } std::lock_guard guard{mutex}; storage.SavePluginPresets(pluginPresets.pluginUri_,pluginPresets); FirePluginPresetsChanged(pluginPresets.pluginUri_); } int64_t PiPedalModel::UploadPreset(const BankFile &bankFile, int64_t uploadAfter) { std::lock_guard guard{mutex}; int64_t newPreset = this->storage.UploadPreset(bankFile, uploadAfter); FirePresetsChanged(-1); return newPreset; } int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter) { std::lock_guard guard{mutex}; int64_t newPreset = this->storage.UploadBank(bankFile, uploadAfter); FireBanksChanged(-1); return newPreset; } void PiPedalModel::LoadPreset(int64_t clientId, int64_t instanceId) { std::lock_guard guard{mutex}; if (storage.LoadPreset(instanceId)) { this->pedalBoard = storage.GetCurrentPreset(); UpdateDefaults(&this->pedalBoard); this->hasPresetChanged = false; // no fire. this->FirePedalBoardChanged(clientId); this->FirePresetsChanged(clientId); // fire now. } } int64_t PiPedalModel::CopyPreset(int64_t clientId, int64_t from, int64_t to) { std::lock_guard guard{mutex}; int64_t result = storage.CopyPreset(from, to); if (result != -1) { this->FirePresetsChanged(clientId); // fire now. } else { throw PiPedalStateException("Copy failed."); } return result; } bool PiPedalModel::UpdatePresets(int64_t clientId, const PresetIndex &presets) { std::lock_guard guard{mutex}; storage.SetPresetIndex(presets); FirePresetsChanged(clientId); return true; } void PiPedalModel::MoveBank(int64_t clientId, int from, int to) { std::lock_guard guard{mutex}; storage.MoveBank(from, to); FireBanksChanged(clientId); } int64_t PiPedalModel::DeleteBank(int64_t clientId, int64_t instanceId) { std::lock_guard guard{mutex}; int64_t selectedBank = this->storage.GetBanks().selectedBank(); int64_t newSelection = storage.DeleteBank(instanceId); int64_t newSelectedBank = this->storage.GetBanks().selectedBank(); this->FireBanksChanged(clientId); // fire now. if (newSelectedBank != selectedBank) { this->OpenBank(clientId, newSelectedBank); } return newSelection; } int64_t PiPedalModel::DeletePreset(int64_t clientId, int64_t instanceId) { std::lock_guard guard{mutex}; int64_t newSelection = storage.DeletePreset(instanceId); this->FirePresetsChanged(clientId); // fire now. return newSelection; } bool PiPedalModel::RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name) { std::lock_guard guard(mutex); if (storage.RenamePreset(instanceId, name)) { this->FirePresetsChanged(clientId); return true; } else { throw PiPedalStateException("Rename failed."); } } void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings) { std::lock_guard guard(mutex); ShutdownClient::SetWifiConfig(wifiConfigSettings); this->storage.SetWifiConfigSettings(wifiConfigSettings); { IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); WifiConfigSettings tWifiConfigSettings = storage.GetWifiConfigSettings(); // (the passwordless version) for (size_t i = 0; i < n; ++i) { t[i]->OnWifiConfigSettingsChanged(tWifiConfigSettings); } delete[] t; } } WifiConfigSettings PiPedalModel::GetWifiConfigSettings() { std::lock_guard guard(mutex); return this->storage.GetWifiConfigSettings(); } JackConfiguration PiPedalModel::GetJackConfiguration() { std::lock_guard guard(mutex); // copy atomically. return this->jackConfiguration; } void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection) { std::lock_guard guard(mutex); // copy atomically. this->storage.SetJackChannelSelection(channelSelection); this->FireChannelSelectionChanged(clientId); this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); if (this->jackHost->IsOpen()) { // do a complete reload. this->jackHost->Close(); this->jackHost->SetPedalBoard(nullptr); this->jackHost->Open(channelSelection); std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; this->lv2PedalBoard = lv2PedalBoard; jackHost->SetPedalBoard(lv2PedalBoard); this->UpdateRealtimeVuSubscriptions(); } } void PiPedalModel::FireChannelSelectionChanged(int64_t clientId) { std::lock_guard guard{mutex}; { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); JackChannelSelection channelSelection = storage.GetJackChannelSelection(this->jackConfiguration); for (size_t i = 0; i < n; ++i) { t[i]->OnChannelSelectionChanged(clientId, channelSelection); } delete[] t; } } JackChannelSelection PiPedalModel::GetJackChannelSelection() { std::lock_guard guard(mutex); JackChannelSelection t = this->storage.GetJackChannelSelection(this->jackConfiguration); if (this->jackConfiguration.isValid()) { t = t.RemoveInvalidChannels(this->jackConfiguration); } return t; } int64_t PiPedalModel::AddVuSubscription(int64_t instanceId) { std::lock_guard guard(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeVuSubscriptions.push_back(VuSubscription{subscriptionId, instanceId}); UpdateRealtimeVuSubscriptions(); return subscriptionId; } void PiPedalModel::RemoveVuSubscription(int64_t subscriptionHandle) { std::lock_guard guard(mutex); for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i) { if ((*i).subscriptionHandle == subscriptionHandle) { activeVuSubscriptions.erase(i); break; } } UpdateRealtimeVuSubscriptions(); } void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) { std::lock_guard guard(mutex); PedalBoardItem *item = this->pedalBoard.GetItem(instanceId); if (item) { const Lv2PluginInfo *pPluginInfo; if (item->uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPluginInfo = GetSplitterPluginInfo(); } else { auto pluginInfo = lv2Host.GetPluginInfo(item->uri()); pPluginInfo = pluginInfo.get(); } if (pPluginInfo) { if (portIndex == -1) { // bypass! this->pedalBoard.SetItemEnabled(instanceId, value != 0); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnItemEnabledChanged(-1, instanceId, value != 0); } delete[] t; this->SetPresetChanged(-1, true); return; } else { for (int i = 0; i < pPluginInfo->ports().size(); ++i) { auto &port = pPluginInfo->ports()[i]; if (port->index() == portIndex) { std::string symbol = port->symbol(); this->pedalBoard.SetControlValue(instanceId, symbol, value); { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnMidiValueChanged(instanceId, symbol, value); } delete[] t; this->SetPresetChanged(-1, true); return; } } } } } } } void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) { std::lock_guard guard(mutex); for (size_t i = 0; i < updates.size(); ++i) { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnVuMeterUpdate(updates); } delete[] t; } } void PiPedalModel::UpdateRealtimeVuSubscriptions() { std::set addedInstances; for (int i = 0; i < activeVuSubscriptions.size(); ++i) { auto instanceId = activeVuSubscriptions[i].instanceid; if (pedalBoard.HasItem(instanceId)) { addedInstances.insert(activeVuSubscriptions[i].instanceid); } } std::vector instanceids(addedInstances.begin(), addedInstances.end()); jackHost->SetVuSubscriptions(instanceids); } void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions() { jackHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions); } int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate) { std::lock_guard guard(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeMonitorPortSubscriptions.push_back( MonitorPortSubscription{subscriptionId, instanceId, key, updateInterval, onUpdate}); UpdateRealtimeMonitorPortSubscriptions(); return subscriptionId; } void PiPedalModel::UnmonitorPort(int64_t subscriptionHandle) { std::lock_guard guard(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { if ((*i).subscriptionHandle == subscriptionHandle) { activeMonitorPortSubscriptions.erase(i); UpdateRealtimeMonitorPortSubscriptions(); break; } } } void PiPedalModel::OnNotifyMonitorPort(const MonitorPortUpdate &update) { std::lock_guard guard(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { if ((*i).subscriptionHandle == update.subscriptionHandle) { // make the call ONLY if the subscription handle is still valid. (*update.callbackPtr)(update.subscriptionHandle, update.value); break; } } } void PiPedalModel::GetLv2Parameter( int64_t clientId, int64_t instanceId, const std::string uri, std::function onSuccess, std::function onError) { std::function onRequestComplete{ [this](RealtimeParameterRequest *pParameter) { { std::lock_guard guard(mutex); bool cancelled = true; for (auto i = this->outstandingParameterRequests.begin(); i != this->outstandingParameterRequests.end(); ++i) { if ((*i) == pParameter) { cancelled = false; this->outstandingParameterRequests.erase(i); break; } } if (!cancelled) { if (pParameter->errorMessage != nullptr) { pParameter->onError(pParameter->errorMessage); } else if (pParameter->responseLength == 0) { pParameter->onError("No response."); } else { pParameter->onSuccess(pParameter->jsonResponse); } } delete pParameter; } }}; LV2_URID urid = this->lv2Host.GetLv2Urid(uri.c_str()); RealtimeParameterRequest *request = new RealtimeParameterRequest( onRequestComplete, clientId, instanceId, urid, onSuccess, onError); std::lock_guard guard(mutex); outstandingParameterRequests.push_back(request); this->jackHost->getRealtimeParameter(request); } BankIndex PiPedalModel::GetBankIndex() const { std::lock_guard guard(const_cast(mutex)); return storage.GetBanks(); } void PiPedalModel::RenameBank(int64_t clientId, int64_t bankId, const std::string &newName) { std::lock_guard guard(mutex); storage.RenameBank(bankId, newName); FireBanksChanged(clientId); } int64_t PiPedalModel::SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName) { std::lock_guard guard(mutex); int64_t newId = storage.SaveBankAs(bankId, newName); FireBanksChanged(clientId); return newId; } void PiPedalModel::OpenBank(int64_t clientId, int64_t bankId) { std::lock_guard guard(mutex); storage.LoadBank(bankId); FireBanksChanged(clientId); FirePresetsChanged(clientId); this->pedalBoard = storage.GetCurrentPreset(); UpdateDefaults(&this->pedalBoard); this->hasPresetChanged = false; this->FirePedalBoardChanged(clientId); } JackServerSettings PiPedalModel::GetJackServerSettings() { std::lock_guard guard(mutex); return this->jackServerSettings; } void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSettings) { std::lock_guard guard(mutex); if (!ShutdownClient::CanUseShutdownClient()) { throw PiPedalException("Can't change server settings when running a debug server."); } this->jackServerSettings = jackServerSettings; // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnJackServerSettingsChanged(jackServerSettings); } delete[] t; if (ShutdownClient::CanUseShutdownClient()) { // save the current (edited) preset now in case the service shutdown isn't clean. CurrentPreset currentPreset; currentPreset.modified_ = this->hasPresetChanged; currentPreset.preset_ = this->pedalBoard; storage.SaveCurrentPreset(currentPreset); this->jackConfiguration.SetIsRestarting(true); FireJackConfigurationChanged(this->jackConfiguration); this->jackHost->UpdateServerConfiguration( jackServerSettings, [this](bool success, const std::string &errorMessage) { std::lock_guard guard(mutex); if (!success) { std::stringstream s; s << "UpdateServerconfiguration failed: " << errorMessage; Lv2Log::error(s.str().c_str()); } // Update jack server status. if (!success) { this->jackConfiguration.SetIsRestarting(false); this->jackConfiguration.SetErrorStatus(errorMessage); FireJackConfigurationChanged(this->jackConfiguration); } else { // we now do a complete restart of the services, // so just sit tight and wait for the restart. #ifdef JUNK this->jackConfiguration.SetErrorStatus(""); FireJackConfigurationChanged(this->jackConfiguration); // restart the pedalboard on a new instance. std::shared_ptr lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)}; this->lv2PedalBoard = lv2PedalBoard; jackHost->SetPedalBoard(lv2PedalBoard); UpdateRealtimeVuSubscriptions(); UpdateRealtimeMonitorPortSubscriptions(); #endif } }); } } void PiPedalModel::UpdateDefaults(PedalBoardItem *pedalBoardItem) { std::shared_ptr t = lv2Host.GetPluginInfo(pedalBoardItem->uri()); const Lv2PluginInfo *pPlugin = t.get(); if (pPlugin == nullptr) { if (pedalBoardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPlugin = GetSplitterPluginInfo(); } } if (pPlugin != nullptr) { for (size_t i = 0; i < pPlugin->ports().size(); ++i) { auto port = pPlugin->ports()[i]; if (port->is_control_port() && port->is_input()) { ControlValue *pValue = pedalBoardItem->GetControlValue(port->symbol()); if (pValue == nullptr) { // Missing? Set it to default value. pedalBoardItem->controlValues().push_back( pipedal::ControlValue(port->symbol().c_str(), port->default_value())); } } } } for (size_t i = 0; i < pedalBoardItem->topChain().size(); ++i) { UpdateDefaults(&(pedalBoardItem->topChain()[i])); } for (size_t i = 0; i < pedalBoardItem->bottomChain().size(); ++i) { UpdateDefaults(&(pedalBoardItem->bottomChain()[i])); } } void PiPedalModel::UpdateDefaults(PedalBoard *pedalBoard) { for (size_t i = 0; i < pedalBoard->items().size(); ++i) { UpdateDefaults(&(pedalBoard->items()[i])); } } PluginPresets PiPedalModel::GetPluginPresets(const std::string& pluginUri) { std::lock_guard guard(mutex); return storage.GetPluginPresets(pluginUri); } PluginUiPresets PiPedalModel::GetPluginUiPresets(const std::string& pluginUri) { std::lock_guard guard(mutex); return storage.GetPluginUiPresets(pluginUri); } void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetInstanceId) { std::lock_guard guard(mutex); PedalBoardItem *pedalBoardItem = this->pedalBoard.GetItem(pluginInstanceId); if (pedalBoardItem != nullptr) { std::vector controlValues = storage.GetPluginPresetValues(pedalBoardItem->uri(), presetInstanceId); jackHost->SetPluginPreset(pluginInstanceId, controlValues); for (size_t i = 0; i < controlValues.size(); ++i) { const ControlValue &value = controlValues[i]; this->pedalBoard.SetControlValue(pluginInstanceId, value.key(), value.value()); } IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; for (size_t i = 0; i < subscribers.size(); ++i) { t[i] = this->subscribers[i]; } size_t n = this->subscribers.size(); for (size_t i = 0; i < n; ++i) { t[i]->OnLoadPluginPreset(pluginInstanceId, controlValues); } delete[] t; } } void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId) { std::lock_guard guard(mutex); for (size_t i = 0; i < atomOutputListeners.size(); ++i) { if (atomOutputListeners[i].clientId == clientId) { atomOutputListeners.erase(atomOutputListeners.begin() + i); --i; } } jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0); } void PiPedalModel::DeleteMidiListeners(int64_t clientId) { std::lock_guard guard(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { if (midiEventListeners[i].clientId == clientId) { midiEventListeners.erase(midiEventListeners.begin() + i); --i; } } jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0); } void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) { std::lock_guard guard(mutex); for (int i = 0; i < atomOutputListeners.size(); ++i) { auto &listener = atomOutputListeners[i]; if (listener.instanceId == instanceId) { auto subscriber = this->GetNotificationSubscriber(listener.clientId); if (subscriber) { subscriber->OnNotifyAtomOutput(listener.clientHandle,instanceId,atomType,atomJson); } else { atomOutputListeners.erase(atomOutputListeners.begin() + i); --i; } } } jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0); } void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) { std::lock_guard guard(mutex); for (int i = 0; i < midiEventListeners.size(); ++i) { auto &listener = midiEventListeners[i]; if ((!isNote) || (!listener.listenForControlsOnly)) { auto subscriber = this->GetNotificationSubscriber(listener.clientId); if (subscriber) { subscriber->OnNotifyMidiListener(listener.clientHandle, isNote, noteOrControl); } else { midiEventListeners.erase(midiEventListeners.begin() + i); --i; } } } jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0); } void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly) { std::lock_guard guard(mutex); MidiListener listener{clientId, clientHandle, listenForControlsOnly}; midiEventListeners.push_back(listener); jackHost->SetListenForMidiEvent(true); } void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId) { std::lock_guard guard(mutex); AtomOutputListener listener{clientId, clientHandle, instanceId}; atomOutputListeners.push_back(listener); jackHost->SetListenForAtomOutput(true); } void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle) { std::lock_guard guard(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { const auto& listener = midiEventListeners[i]; if (listener.clientId == clientId && listener.clientHandle == clientHandle) { midiEventListeners.erase(midiEventListeners.begin() + i); break; } } if (midiEventListeners.size() == 0) { jackHost->SetListenForMidiEvent(false); } } void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle) { std::lock_guard guard(mutex); for (size_t i = 0; i < atomOutputListeners.size(); ++i) { const auto&listener = atomOutputListeners[i]; if (listener.clientId == clientId && listener.clientHandle == clientHandle) { atomOutputListeners.erase(atomOutputListeners.begin() + i); break; } } if (midiEventListeners.size() == 0) { jackHost->SetListenForMidiEvent(false); } } std::vector PiPedalModel::GetAlsaDevices() { return this->alsaDevices.GetAlsaDevices(); } const std::filesystem::path& PiPedalModel::GetWebRoot() const { return webRoot; }