v1.0.18
- MIDI Program/Bank select. - MIDI next/previous program.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022-2023 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
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "Lv2PedalBoard.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include "json.hpp"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "JackServerSettings.hpp"
|
||||
#include <functional>
|
||||
#include "PiPedalAlsa.hpp"
|
||||
@@ -32,6 +32,8 @@
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
struct RealtimeMidiProgramRequest;
|
||||
struct RealtimeNextMidiProgramRequest;
|
||||
|
||||
using PortMonitorCallback = std::function<void (int64_t handle, float value)>;
|
||||
|
||||
@@ -91,7 +93,7 @@ public:
|
||||
|
||||
};
|
||||
|
||||
class IJackHostCallbacks {
|
||||
class IAudioHostCallbacks {
|
||||
public:
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates) = 0;
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0;
|
||||
@@ -99,6 +101,8 @@ public:
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0;
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0;
|
||||
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -124,17 +128,17 @@ public:
|
||||
|
||||
class IHost;
|
||||
|
||||
class JackHost {
|
||||
class AudioHost {
|
||||
protected:
|
||||
JackHost() { }
|
||||
AudioHost() { }
|
||||
public:
|
||||
static JackHost*CreateInstance(IHost *pHost);
|
||||
virtual ~JackHost() { };
|
||||
static AudioHost*CreateInstance(IHost *pHost);
|
||||
virtual ~AudioHost() { };
|
||||
|
||||
virtual void UpdateServerConfiguration(const JackServerSettings & jackServerSettings,
|
||||
std::function<void(bool success, const std::string&errorMessage)> onComplete) = 0;
|
||||
|
||||
virtual void SetNotificationCallbacks(IJackHostCallbacks *pNotifyCallbacks) = 0;
|
||||
virtual void SetNotificationCallbacks(IAudioHostCallbacks *pNotifyCallbacks) = 0;
|
||||
|
||||
virtual void SetListenForMidiEvent(bool listen) = 0;
|
||||
virtual void SetListenForAtomOutput(bool listen) = 0;
|
||||
@@ -158,7 +162,10 @@ public:
|
||||
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) = 0;
|
||||
virtual void SetMonitorPortSubscriptions(const std::vector<MonitorPortSubscription> &subscriptions) = 0;
|
||||
|
||||
virtual void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings) = 0;
|
||||
|
||||
virtual void getRealtimeParameter(RealtimeParameterRequest*pParameterRequest) = 0;
|
||||
virtual void AckMidiProgramRequest(uint64_t requestId) = 0;
|
||||
|
||||
virtual JackHostStatus getJackStatus() = 0;
|
||||
|
||||
+3
-3
@@ -149,7 +149,7 @@ set (PIPEDAL_SOURCES
|
||||
CpuGovernor.cpp CpuGovernor.hpp
|
||||
GovernorSettings.cpp GovernorSettings.hpp
|
||||
SysExec.cpp SysExec.hpp
|
||||
BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp
|
||||
WebServer.cpp WebServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp
|
||||
WifiConfigSettings.hpp WifiConfigSettings.cpp
|
||||
WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp
|
||||
ConfigUtil.hpp ConfigUtil.cpp
|
||||
@@ -164,7 +164,7 @@ set (PIPEDAL_SOURCES
|
||||
Presets.hpp Presets.cpp
|
||||
Storage.hpp Storage.cpp
|
||||
Banks.hpp Banks.cpp
|
||||
JackHost.hpp JackHost.cpp
|
||||
AudioHost.hpp AudioHost.cpp
|
||||
JackConfiguration.hpp JackConfiguration.cpp
|
||||
defer.hpp
|
||||
Lv2Effect.cpp Lv2Effect.hpp
|
||||
@@ -264,7 +264,7 @@ add_executable(pipedaltest testMain.cpp
|
||||
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
SystemConfigFileTest.cpp
|
||||
BeastServerTest.cpp
|
||||
WebServerTest.cpp
|
||||
MemDebug.cpp
|
||||
MemDebug.hpp
|
||||
)
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@
|
||||
#include "lv2/parameters.lv2/parameters.h"
|
||||
#include "lv2/units.lv2/units.h"
|
||||
#include "lv2/atom.lv2/util.h"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include <exception>
|
||||
#include "RingBufferReader.hpp"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "SplitEffect.hpp"
|
||||
#include "RingBufferReader.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "Lv2EventBufferWriter.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
+9
-3
@@ -58,15 +58,21 @@ public:
|
||||
private:
|
||||
std::string symbol_;
|
||||
int channel_ = -1;
|
||||
int bindingType_;
|
||||
int bindingType_ = BINDING_TYPE_NONE;
|
||||
int note_ = 12*4+24;
|
||||
int control_ = 1;
|
||||
float minValue_ = 0;
|
||||
float maxValue_ = 1;
|
||||
float rotaryScale_ = 1;
|
||||
int linearControlType_ = 0;
|
||||
int switchControlType_ = 0;
|
||||
int linearControlType_ = LINEAR_CONTROL_TYPE;
|
||||
int switchControlType_ = LATCH_CONTROL_TYPE;
|
||||
public:
|
||||
static MidiBinding SystemBinding(const std::string&symbol)
|
||||
{
|
||||
MidiBinding result;
|
||||
result.symbol_ = symbol;
|
||||
return result;
|
||||
}
|
||||
GETTER_SETTER(channel);
|
||||
GETTER_SETTER_REF(symbol);
|
||||
GETTER_SETTER(bindingType);
|
||||
|
||||
+151
-34
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022-2023 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
|
||||
@@ -23,13 +23,14 @@
|
||||
#include "ConfigUtil.hpp"
|
||||
#include <sched.h>
|
||||
#include "PiPedalModel.hpp"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include <set>
|
||||
#include "PiPedalConfiguration.hpp"
|
||||
#include "AdminClient.hpp"
|
||||
#include "SplitEffect.hpp"
|
||||
#include "CpuGovernor.hpp"
|
||||
#include "RingBufferReader.hpp"
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
@@ -84,9 +85,9 @@ void PiPedalModel::Close()
|
||||
}
|
||||
delete[] t;
|
||||
|
||||
if (jackHost)
|
||||
if (audioHost)
|
||||
{
|
||||
jackHost->Close();
|
||||
audioHost->Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +114,9 @@ PiPedalModel::~PiPedalModel()
|
||||
|
||||
try
|
||||
{
|
||||
if (jackHost)
|
||||
if (audioHost)
|
||||
{
|
||||
jackHost->Close();
|
||||
audioHost->Close();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -133,6 +134,8 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
|
||||
storage.SetDataRoot(configuration.GetLocalStoragePath());
|
||||
storage.Initialize();
|
||||
|
||||
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
||||
|
||||
this->jackServerSettings.ReadJackConfiguration();
|
||||
}
|
||||
|
||||
@@ -191,10 +194,14 @@ void PiPedalModel::Load()
|
||||
}
|
||||
UpdateDefaults(&this->pedalBoard);
|
||||
|
||||
std::unique_ptr<JackHost> p{JackHost::CreateInstance(lv2Host.asIHost())};
|
||||
this->jackHost = std::move(p);
|
||||
std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(lv2Host.asIHost())};
|
||||
this->audioHost = std::move(p);
|
||||
|
||||
this->jackHost->SetNotificationCallbacks(this);
|
||||
this->audioHost->SetNotificationCallbacks(this);
|
||||
|
||||
this->systemMidiBindings = storage.GetSystemMidiBindings();
|
||||
|
||||
this->audioHost->SetSystemMidiBindings(this->systemMidiBindings);
|
||||
|
||||
if (configuration.GetMLock())
|
||||
{
|
||||
@@ -226,11 +233,11 @@ void PiPedalModel::Load()
|
||||
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection);
|
||||
try
|
||||
{
|
||||
jackHost->Open(this->jackServerSettings, selection);
|
||||
audioHost->Open(this->jackServerSettings, selection);
|
||||
try {
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
audioHost->SetPedalBoard(lv2PedalBoard);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -305,7 +312,7 @@ void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const s
|
||||
effect->SetControl(index,value);;
|
||||
}
|
||||
} else {
|
||||
jackHost->SetControlValue(pedalItemId, symbol, value);
|
||||
audioHost->SetControlValue(pedalItemId, symbol, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,11 +394,11 @@ void PiPedalModel::FirePedalBoardChanged(int64_t clientId)
|
||||
delete[] t;
|
||||
|
||||
// notify the audio thread.
|
||||
if (jackHost->IsOpen())
|
||||
if (audioHost->IsOpen())
|
||||
{
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
audioHost->SetPedalBoard(lv2PedalBoard);
|
||||
UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
}
|
||||
@@ -446,7 +453,7 @@ void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId
|
||||
this->SetPresetChanged(clientId, true);
|
||||
|
||||
// Notify audo thread.
|
||||
this->jackHost->SetBypass(pedalItemId, enabled);
|
||||
this->audioHost->SetBypass(pedalItemId, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,6 +628,87 @@ int64_t PiPedalModel::UploadBank(BankFile &bankFile, int64_t uploadAfter)
|
||||
return newPreset;
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
try {
|
||||
PresetIndex index;
|
||||
storage.GetPresetIndex(&index);
|
||||
auto currentPresetId = storage.GetCurrentPresetId();
|
||||
size_t currentPresetIndex = 0;
|
||||
|
||||
for (size_t i = 0; i < index.presets().size(); ++i)
|
||||
{
|
||||
if (index.presets()[i].instanceId() == currentPresetId)
|
||||
{
|
||||
currentPresetIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index.presets().size() == 0)
|
||||
{
|
||||
throw PiPedalException("No presets loaded.");
|
||||
}
|
||||
if (request.direction < 0)
|
||||
{
|
||||
if (currentPresetIndex == 0)
|
||||
{
|
||||
currentPresetIndex = index.presets().size()-1;
|
||||
} else {
|
||||
--currentPresetIndex;
|
||||
}
|
||||
} else {
|
||||
++currentPresetIndex;
|
||||
if (currentPresetIndex >= index.presets().size())
|
||||
{
|
||||
currentPresetIndex = 0;
|
||||
}
|
||||
}
|
||||
LoadPreset(-1,index.presets()[currentPresetIndex].instanceId());
|
||||
} catch (std::exception&e)
|
||||
{
|
||||
|
||||
Lv2Log::error(e.what());
|
||||
}
|
||||
if (this->audioHost)
|
||||
{
|
||||
this->audioHost->AckMidiProgramRequest(request.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PiPedalModel::OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
try {
|
||||
if (midiProgramRequest.bank >= 0)
|
||||
{
|
||||
int64_t bankId = storage.GetBankByMidiBankNumber(midiProgramRequest.bank);
|
||||
if (bankId == -1) throw PiPedalException("Bank not found.");
|
||||
if (bankId != this->storage.GetBanks().selectedBank())
|
||||
{
|
||||
storage.LoadBank(bankId);
|
||||
|
||||
FireBanksChanged(-1);
|
||||
FirePresetsChanged(-1);
|
||||
}
|
||||
}
|
||||
int64_t presetId = storage.GetPresetByProgramNumber(midiProgramRequest.program);
|
||||
if (presetId == -1) throw PiPedalException("No valid preset.");
|
||||
LoadPreset(-1,presetId);
|
||||
} catch (std::exception&e)
|
||||
{
|
||||
|
||||
Lv2Log::error(e.what());
|
||||
}
|
||||
if (this->audioHost)
|
||||
{
|
||||
this->audioHost->AckMidiProgramRequest(midiProgramRequest.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PiPedalModel::LoadPreset(int64_t clientId, int64_t instanceId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
@@ -883,10 +971,10 @@ JackConfiguration PiPedalModel::GetJackConfiguration()
|
||||
|
||||
void PiPedalModel::RestartAudio()
|
||||
{
|
||||
if (this->jackHost->IsOpen())
|
||||
if (this->audioHost->IsOpen())
|
||||
{
|
||||
|
||||
this->jackHost->Close();
|
||||
this->audioHost->Close();
|
||||
}
|
||||
// restarting is a bit dodgy. It was impossible with Jack, but
|
||||
// now very plausible with the ALSA audio stack.
|
||||
@@ -899,7 +987,7 @@ void PiPedalModel::RestartAudio()
|
||||
|
||||
// do a complete reload.
|
||||
|
||||
this->jackHost->SetPedalBoard(nullptr);
|
||||
this->audioHost->SetPedalBoard(nullptr);
|
||||
|
||||
this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
|
||||
|
||||
@@ -928,13 +1016,13 @@ void PiPedalModel::RestartAudio()
|
||||
Lv2Log::error("Audio configuration not valid.");
|
||||
return;
|
||||
}
|
||||
this->jackHost->Open(this->jackServerSettings, channelSelection);
|
||||
this->audioHost->Open(this->jackServerSettings, channelSelection);
|
||||
|
||||
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection);
|
||||
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
audioHost->SetPedalBoard(lv2PedalBoard);
|
||||
this->UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
}
|
||||
@@ -1121,12 +1209,12 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
|
||||
}
|
||||
}
|
||||
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
|
||||
jackHost->SetVuSubscriptions(instanceids);
|
||||
audioHost->SetVuSubscriptions(instanceids);
|
||||
}
|
||||
|
||||
void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions()
|
||||
{
|
||||
jackHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
|
||||
audioHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
|
||||
}
|
||||
|
||||
int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate)
|
||||
@@ -1220,7 +1308,7 @@ void PiPedalModel::GetLv2Parameter(
|
||||
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
outstandingParameterRequests.push_back(request);
|
||||
this->jackHost->getRealtimeParameter(request);
|
||||
this->audioHost->getRealtimeParameter(request);
|
||||
}
|
||||
|
||||
BankIndex PiPedalModel::GetBankIndex() const
|
||||
@@ -1254,6 +1342,7 @@ void PiPedalModel::OpenBank(int64_t clientId, int64_t bankId)
|
||||
FirePresetsChanged(clientId);
|
||||
|
||||
this->pedalBoard = storage.GetCurrentPreset();
|
||||
|
||||
UpdateDefaults(&this->pedalBoard);
|
||||
this->hasPresetChanged = false;
|
||||
this->FirePedalBoardChanged(clientId);
|
||||
@@ -1311,7 +1400,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
||||
|
||||
this->jackConfiguration.SetIsRestarting(true);
|
||||
FireJackConfigurationChanged(this->jackConfiguration);
|
||||
this->jackHost->UpdateServerConfiguration(
|
||||
this->audioHost->UpdateServerConfiguration(
|
||||
jackServerSettings,
|
||||
[this](bool success, const std::string &errorMessage)
|
||||
{
|
||||
@@ -1341,7 +1430,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
|
||||
this->lv2PedalBoard = lv2PedalBoard;
|
||||
|
||||
jackHost->SetPedalBoard(lv2PedalBoard);
|
||||
audioHost->SetPedalBoard(lv2PedalBoard);
|
||||
UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
#endif
|
||||
@@ -1418,7 +1507,7 @@ void PiPedalModel::LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetIns
|
||||
if (pedalBoardItem != nullptr)
|
||||
{
|
||||
std::vector<ControlValue> controlValues = storage.GetPluginPresetValues(pedalBoardItem->uri(), presetInstanceId);
|
||||
jackHost->SetPluginPreset(pluginInstanceId, controlValues);
|
||||
audioHost->SetPluginPreset(pluginInstanceId, controlValues);
|
||||
|
||||
for (size_t i = 0; i < controlValues.size(); ++i)
|
||||
{
|
||||
@@ -1451,7 +1540,7 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId)
|
||||
--i;
|
||||
}
|
||||
}
|
||||
jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
|
||||
void PiPedalModel::DeleteMidiListeners(int64_t clientId)
|
||||
@@ -1465,7 +1554,7 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
|
||||
--i;
|
||||
}
|
||||
}
|
||||
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
||||
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson)
|
||||
@@ -1489,7 +1578,7 @@ void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string &at
|
||||
}
|
||||
}
|
||||
}
|
||||
jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
||||
@@ -1513,7 +1602,7 @@ void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl)
|
||||
}
|
||||
}
|
||||
}
|
||||
jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
||||
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
|
||||
}
|
||||
|
||||
void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly)
|
||||
@@ -1521,7 +1610,7 @@ void PiPedalModel::ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bo
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
MidiListener listener{clientId, clientHandle, listenForControlsOnly};
|
||||
midiEventListeners.push_back(listener);
|
||||
jackHost->SetListenForMidiEvent(true);
|
||||
audioHost->SetListenForMidiEvent(true);
|
||||
}
|
||||
|
||||
void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId)
|
||||
@@ -1529,7 +1618,7 @@ void PiPedalModel::ListenForAtomOutputs(int64_t clientId, int64_t clientHandle,
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
AtomOutputListener listener{clientId, clientHandle, instanceId};
|
||||
atomOutputListeners.push_back(listener);
|
||||
jackHost->SetListenForAtomOutput(true);
|
||||
audioHost->SetListenForAtomOutput(true);
|
||||
}
|
||||
|
||||
void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle)
|
||||
@@ -1546,7 +1635,7 @@ void PiPedalModel::CancelListenForMidiEvent(int64_t clientId, int64_t clientHand
|
||||
}
|
||||
if (midiEventListeners.size() == 0)
|
||||
{
|
||||
jackHost->SetListenForMidiEvent(false);
|
||||
audioHost->SetListenForMidiEvent(false);
|
||||
}
|
||||
}
|
||||
void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle)
|
||||
@@ -1563,7 +1652,7 @@ void PiPedalModel::CancelListenForAtomOutputs(int64_t clientId, int64_t clientHa
|
||||
}
|
||||
if (midiEventListeners.size() == 0)
|
||||
{
|
||||
jackHost->SetListenForMidiEvent(false);
|
||||
audioHost->SetListenForMidiEvent(false);
|
||||
}
|
||||
}
|
||||
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
|
||||
@@ -1600,3 +1689,31 @@ void PiPedalModel::SetFavorites(const std::map<std::string, bool> &favorites)
|
||||
t[i]->OnFavoritesChanged(favorites);
|
||||
}
|
||||
}
|
||||
std::vector<MidiBinding> PiPedalModel::GetSystemMidiBidings() {
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
return this->systemMidiBindings;
|
||||
}
|
||||
void PiPedalModel::SetSystemMidiBindings(std::vector<MidiBinding> &bindings)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
this->systemMidiBindings = bindings;
|
||||
storage.SetSystemMidiBindings(bindings);
|
||||
if (this->audioHost)
|
||||
{
|
||||
this->audioHost->SetSystemMidiBindings(bindings);
|
||||
}
|
||||
|
||||
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]->OnSystemMidiBindingsChanged(bindings);
|
||||
}
|
||||
delete[] t;
|
||||
|
||||
|
||||
}
|
||||
+239
-235
@@ -25,7 +25,7 @@
|
||||
#include "Storage.hpp"
|
||||
#include "Banks.hpp"
|
||||
#include "JackConfiguration.hpp"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
@@ -38,245 +38,249 @@
|
||||
#include "AvahiService.hpp"
|
||||
#include <thread>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
struct RealtimeMidiProgramRequest;
|
||||
struct RealtimeNextMidiProgramRequest;
|
||||
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
|
||||
|
||||
class IPiPedalModelSubscriber {
|
||||
public:
|
||||
virtual int64_t GetClientId() = 0;
|
||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string&state) = 0;
|
||||
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
|
||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex&presets) = 0;
|
||||
virtual void OnPluginPresetsChanged(const std::string&pluginUri) = 0;
|
||||
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection&channelSelection) = 0;
|
||||
virtual void OnVuMeterUpdate(const std::vector<VuUpdate>& updates) = 0;
|
||||
virtual void OnBankIndexChanged(const BankIndex& bankIndex) = 0;
|
||||
virtual void OnJackServerSettingsChanged(const JackServerSettings& jackServerSettings) = 0;
|
||||
virtual void OnJackConfigurationChanged(const JackConfiguration& jackServerConfiguration) = 0;
|
||||
virtual void OnLoadPluginPreset(int64_t instanceId,const std::vector<ControlValue>&controlValues) = 0;
|
||||
virtual void OnMidiValueChanged(int64_t instanceId, const std::string&symbol, float value) = 0;
|
||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
|
||||
virtual void OnNotifyAtomOutput(int64_t clientModel,uint64_t instanceId,const std::string&atomType, const std::string&atomJson) = 0;
|
||||
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) = 0;
|
||||
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings&wifiDirectConfigSettings) = 0;
|
||||
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
||||
virtual void OnFavoritesChanged(const std::map<std::string,bool> &favorites) = 0;
|
||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
class PiPedalModel: private IJackHostCallbacks {
|
||||
private:
|
||||
std::unique_ptr<std::jthread> pingThread;
|
||||
|
||||
AvahiService avahiService;
|
||||
uint16_t webPort;
|
||||
|
||||
PiPedalAlsaDevices alsaDevices;
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
AdminClient adminClient;
|
||||
|
||||
|
||||
class MidiListener {
|
||||
class IPiPedalModelSubscriber
|
||||
{
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
bool listenForControlsOnly;
|
||||
virtual int64_t GetClientId() = 0;
|
||||
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
|
||||
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string &state) = 0;
|
||||
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
|
||||
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
|
||||
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
|
||||
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
|
||||
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
|
||||
virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0;
|
||||
virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0;
|
||||
virtual void OnJackConfigurationChanged(const JackConfiguration &jackServerConfiguration) = 0;
|
||||
virtual void OnLoadPluginPreset(int64_t instanceId, const std::vector<ControlValue> &controlValues) = 0;
|
||||
virtual void OnMidiValueChanged(int64_t instanceId, const std::string &symbol, float value) = 0;
|
||||
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0;
|
||||
virtual void OnNotifyAtomOutput(int64_t clientModel, uint64_t instanceId, const std::string &atomType, const std::string &atomJson) = 0;
|
||||
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings &wifiConfigSettings) = 0;
|
||||
virtual void OnWifiDirectConfigSettingsChanged(const WifiDirectConfigSettings &wifiDirectConfigSettings) = 0;
|
||||
virtual void OnGovernorSettingsChanged(const std::string &governor) = 0;
|
||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
|
||||
virtual void OnShowStatusMonitorChanged(bool show) = 0;
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
class AtomOutputListener {
|
||||
|
||||
class PiPedalModel : private IAudioHostCallbacks
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<std::jthread> pingThread;
|
||||
|
||||
std::vector<MidiBinding> systemMidiBindings;
|
||||
|
||||
AvahiService avahiService;
|
||||
uint16_t webPort;
|
||||
|
||||
PiPedalAlsaDevices alsaDevices;
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
AdminClient adminClient;
|
||||
|
||||
class MidiListener
|
||||
{
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
bool listenForControlsOnly;
|
||||
};
|
||||
class AtomOutputListener
|
||||
{
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
uint64_t instanceId;
|
||||
};
|
||||
void DeleteMidiListeners(int64_t clientId);
|
||||
void DeleteAtomOutputListeners(int64_t clientId);
|
||||
|
||||
std::vector<MidiListener> midiEventListeners;
|
||||
std::vector<AtomOutputListener> atomOutputListeners;
|
||||
|
||||
JackServerSettings jackServerSettings;
|
||||
PiPedalHost lv2Host;
|
||||
PedalBoard pedalBoard;
|
||||
Storage storage;
|
||||
bool hasPresetChanged = false;
|
||||
|
||||
std::unique_ptr<AudioHost> audioHost;
|
||||
JackConfiguration jackConfiguration;
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
std::vector<IPiPedalModelSubscriber *> subscribers;
|
||||
void SetPresetChanged(int64_t clientId, bool value);
|
||||
void FirePresetsChanged(int64_t clientId);
|
||||
void FirePluginPresetsChanged(const std::string &pluginUri);
|
||||
void FirePedalBoardChanged(int64_t clientId);
|
||||
void FireChannelSelectionChanged(int64_t clientId);
|
||||
void FireBanksChanged(int64_t clientId);
|
||||
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
|
||||
|
||||
void UpdateDefaults(PedalBoardItem *pedalBoardItem);
|
||||
void UpdateDefaults(PedalBoard *pedalBoard);
|
||||
|
||||
class VuSubscription
|
||||
{
|
||||
public:
|
||||
int64_t subscriptionHandle;
|
||||
int64_t instanceid;
|
||||
};
|
||||
int64_t nextSubscriptionId = 1;
|
||||
std::vector<VuSubscription> activeVuSubscriptions;
|
||||
|
||||
std::vector<MonitorPortSubscription> activeMonitorPortSubscriptions;
|
||||
|
||||
void UpdateRealtimeVuSubscriptions();
|
||||
void UpdateRealtimeMonitorPortSubscriptions();
|
||||
void OnVuUpdate(const std::vector<VuUpdate> &updates);
|
||||
|
||||
void RestartAudio();
|
||||
|
||||
std::vector<RealtimeParameterRequest *> outstandingParameterRequests;
|
||||
|
||||
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
|
||||
|
||||
private: // IAudioHostCallbacks
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> &updates);
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string &atomType, const std::string &atomJson);
|
||||
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest &midiProgramRequest);
|
||||
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request);
|
||||
|
||||
void UpdateVst3Settings(PedalBoard &pedalBoard);
|
||||
|
||||
PiPedalConfiguration configuration;
|
||||
|
||||
public:
|
||||
int64_t clientId;
|
||||
int64_t clientHandle;
|
||||
uint64_t instanceId;
|
||||
PiPedalModel();
|
||||
virtual ~PiPedalModel();
|
||||
|
||||
uint16_t GetWebPort() const { return webPort; }
|
||||
void Close();
|
||||
|
||||
void UpdateDnsSd();
|
||||
|
||||
AdminClient &GetAdminClient() { return adminClient; }
|
||||
|
||||
void Init(const PiPedalConfiguration &configuration);
|
||||
|
||||
void LoadLv2PluginInfo();
|
||||
void Load();
|
||||
|
||||
const PiPedalHost &GetLv2Host() const { return lv2Host; }
|
||||
PedalBoard GetCurrentPedalBoardCopy()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
return pedalBoard; // can return a referece because we'd lose mutex protection
|
||||
}
|
||||
PluginUiPresets GetPluginUiPresets(const std::string &pluginUri);
|
||||
PluginPresets GetPluginPresets(const std::string &pluginUri);
|
||||
|
||||
void LoadPluginPreset(int64_t pluginInstanceId, uint64_t presetInstanceId);
|
||||
|
||||
void AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber);
|
||||
void RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber);
|
||||
|
||||
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
void PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value);
|
||||
void SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
|
||||
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
|
||||
|
||||
void GetPresets(PresetIndex *pResult);
|
||||
|
||||
PedalBoard GetPreset(int64_t instanceId);
|
||||
void GetBank(int64_t instanceId, BankFile *pBank);
|
||||
|
||||
int64_t UploadBank(BankFile &bankFile, int64_t uploadAfter = -1);
|
||||
int64_t UploadPreset(const BankFile &bankFile, int64_t uploadAfter = -1);
|
||||
void UploadPluginPresets(const PluginPresets &pluginPresets);
|
||||
void SaveCurrentPreset(int64_t clientId);
|
||||
int64_t SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId = -1);
|
||||
int64_t SavePluginPresetAs(int64_t instanceId, const std::string &name);
|
||||
|
||||
void LoadPreset(int64_t clientId, int64_t instanceId);
|
||||
bool UpdatePresets(int64_t clientId, const PresetIndex &presets);
|
||||
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
|
||||
int64_t DeletePreset(int64_t clientId, int64_t instanceId);
|
||||
bool RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name);
|
||||
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
|
||||
uint64_t CopyPluginPreset(const std::string &pluginUri, uint64_t presetId);
|
||||
|
||||
void MoveBank(int64_t clientId, int from, int to);
|
||||
int64_t DeleteBank(int64_t clientId, int64_t instanceId);
|
||||
|
||||
int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId, instanceId, -1); }
|
||||
|
||||
JackConfiguration GetJackConfiguration();
|
||||
|
||||
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
|
||||
JackChannelSelection GetJackChannelSelection();
|
||||
|
||||
void SetShowStatusMonitor(bool show);
|
||||
bool GetShowStatusMonitor();
|
||||
|
||||
void SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings);
|
||||
WifiConfigSettings GetWifiConfigSettings();
|
||||
|
||||
void SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiConfigSettings);
|
||||
WifiDirectConfigSettings GetWifiDirectConfigSettings();
|
||||
|
||||
void SetGovernorSettings(const std::string &governor);
|
||||
GovernorSettings GetGovernorSettings();
|
||||
|
||||
int64_t AddVuSubscription(int64_t instanceId);
|
||||
void RemoveVuSubscription(int64_t subscriptionHandle);
|
||||
|
||||
void SetSystemMidiBindings(std::vector<MidiBinding> &bindings);
|
||||
std::vector<MidiBinding> GetSystemMidiBidings();
|
||||
|
||||
int64_t MonitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate);
|
||||
void UnmonitorPort(int64_t subscriptionHandle);
|
||||
|
||||
void GetLv2Parameter(
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess,
|
||||
std::function<void(const std::string &error)> onError);
|
||||
|
||||
BankIndex GetBankIndex() const;
|
||||
void RenameBank(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||
int64_t SaveBankAs(int64_t clientId, int64_t bankId, const std::string &newName);
|
||||
void OpenBank(int64_t clientId, int64_t bankId);
|
||||
|
||||
JackHostStatus GetJackStatus()
|
||||
{
|
||||
return this->audioHost->getJackStatus();
|
||||
}
|
||||
JackServerSettings GetJackServerSettings();
|
||||
void SetJackServerSettings(const JackServerSettings &jackServerSettings);
|
||||
|
||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
|
||||
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
const std::filesystem::path &GetWebRoot() const;
|
||||
|
||||
std::map<std::string, bool> GetFavorites() const;
|
||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||
};
|
||||
void DeleteMidiListeners(int64_t clientId);
|
||||
void DeleteAtomOutputListeners(int64_t clientId);
|
||||
|
||||
std::vector<MidiListener> midiEventListeners;
|
||||
std::vector<AtomOutputListener> atomOutputListeners;
|
||||
|
||||
JackServerSettings jackServerSettings;
|
||||
PiPedalHost lv2Host;
|
||||
PedalBoard pedalBoard;
|
||||
Storage storage;
|
||||
bool hasPresetChanged = false;
|
||||
|
||||
std::unique_ptr<JackHost> jackHost;
|
||||
JackConfiguration jackConfiguration;
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
|
||||
std::vector<IPiPedalModelSubscriber*> subscribers;
|
||||
void SetPresetChanged(int64_t clientId,bool value);
|
||||
void FirePresetsChanged(int64_t clientId);
|
||||
void FirePluginPresetsChanged(const std::string &pluginUri);
|
||||
void FirePedalBoardChanged(int64_t clientId);
|
||||
void FireChannelSelectionChanged(int64_t clientId);
|
||||
void FireBanksChanged(int64_t clientId);
|
||||
void FireJackConfigurationChanged(const JackConfiguration&jackConfiguration);
|
||||
|
||||
void UpdateDefaults(PedalBoardItem*pedalBoardItem);
|
||||
void UpdateDefaults(PedalBoard*pedalBoard);
|
||||
|
||||
|
||||
class VuSubscription {
|
||||
public:
|
||||
int64_t subscriptionHandle;
|
||||
int64_t instanceid;
|
||||
};
|
||||
int64_t nextSubscriptionId = 1;
|
||||
std::vector<VuSubscription> activeVuSubscriptions;
|
||||
|
||||
std::vector<MonitorPortSubscription> activeMonitorPortSubscriptions;
|
||||
|
||||
void UpdateRealtimeVuSubscriptions();
|
||||
void UpdateRealtimeMonitorPortSubscriptions();
|
||||
void OnVuUpdate(const std::vector<VuUpdate>& updates);
|
||||
|
||||
void RestartAudio();
|
||||
|
||||
std::vector<RealtimeParameterRequest*> outstandingParameterRequests;
|
||||
|
||||
IPiPedalModelSubscriber*GetNotificationSubscriber(int64_t clientId);
|
||||
|
||||
private: // IJackHostCallbacks
|
||||
virtual void OnNotifyVusSubscription(const std::vector<VuUpdate> & updates);
|
||||
virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update);
|
||||
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value);
|
||||
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
|
||||
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
|
||||
|
||||
void UpdateVst3Settings(PedalBoard&pedalBoard);
|
||||
|
||||
PiPedalConfiguration configuration;
|
||||
public:
|
||||
PiPedalModel();
|
||||
virtual ~PiPedalModel();
|
||||
|
||||
uint16_t GetWebPort() const { return webPort; }
|
||||
void Close();
|
||||
|
||||
void UpdateDnsSd();
|
||||
|
||||
AdminClient&GetAdminClient() { return adminClient; }
|
||||
|
||||
void Init(const PiPedalConfiguration&configuration);
|
||||
|
||||
void LoadLv2PluginInfo();
|
||||
void Load();
|
||||
|
||||
const PiPedalHost& GetLv2Host() const { return lv2Host; }
|
||||
PedalBoard GetCurrentPedalBoardCopy()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(mutex);
|
||||
return pedalBoard; // can return a referece because we'd lose mutex protection
|
||||
}
|
||||
PluginUiPresets GetPluginUiPresets(const std::string& pluginUri);
|
||||
PluginPresets GetPluginPresets(const std::string&pluginUri);
|
||||
|
||||
void LoadPluginPreset(int64_t pluginInstanceId,uint64_t presetInstanceId);
|
||||
|
||||
void AddNotificationSubscription(IPiPedalModelSubscriber* pSubscriber);
|
||||
void RemoveNotificationSubsription(IPiPedalModelSubscriber* pSubscriber);
|
||||
|
||||
void SetPedalBoardItemEnable(int64_t clientId, int64_t instanceId, bool enabled);
|
||||
void SetControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
|
||||
void PreviewControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
|
||||
void SetPedalBoard(int64_t clientId,PedalBoard &pedalBoard);
|
||||
void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
|
||||
|
||||
|
||||
void GetPresets(PresetIndex*pResult);
|
||||
PedalBoard GetPreset(int64_t instanceId);
|
||||
void GetBank(int64_t instanceId, BankFile*pBank);
|
||||
|
||||
int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter = -1);
|
||||
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter = -1);
|
||||
void UploadPluginPresets(const PluginPresets&pluginPresets);
|
||||
void SaveCurrentPreset(int64_t clientId);
|
||||
int64_t SaveCurrentPresetAs(int64_t clientId, const std::string& name,int64_t saveAfterInstanceId = -1);
|
||||
int64_t SavePluginPresetAs(int64_t instanceId, const std::string&name);
|
||||
|
||||
void LoadPreset(int64_t clientId, int64_t instanceId);
|
||||
bool UpdatePresets(int64_t clientId,const PresetIndex&presets);
|
||||
void UpdatePluginPresets(const PluginUiPresets&pluginPresets);
|
||||
int64_t DeletePreset(int64_t clientId,int64_t instanceId);
|
||||
bool RenamePreset(int64_t clientId,int64_t instanceId,const std::string&name);
|
||||
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
|
||||
uint64_t CopyPluginPreset(const std::string&pluginUri,uint64_t presetId);
|
||||
|
||||
void MoveBank(int64_t clientId,int from, int to);
|
||||
int64_t DeleteBank(int64_t clientId, int64_t instanceId);
|
||||
|
||||
|
||||
int64_t DuplicatePreset(int64_t clientId, int64_t instanceId) { return CopyPreset(clientId,instanceId,-1); }
|
||||
|
||||
JackConfiguration GetJackConfiguration();
|
||||
|
||||
void SetJackChannelSelection(int64_t clientId,const JackChannelSelection &channelSelection);
|
||||
JackChannelSelection GetJackChannelSelection();
|
||||
|
||||
void SetShowStatusMonitor(bool show);
|
||||
bool GetShowStatusMonitor();
|
||||
|
||||
void SetWifiConfigSettings(const WifiConfigSettings&wifiConfigSettings);
|
||||
WifiConfigSettings GetWifiConfigSettings();
|
||||
|
||||
void SetWifiDirectConfigSettings(const WifiDirectConfigSettings&wifiConfigSettings);
|
||||
WifiDirectConfigSettings GetWifiDirectConfigSettings();
|
||||
|
||||
void SetGovernorSettings(const std::string& governor);
|
||||
GovernorSettings GetGovernorSettings();
|
||||
|
||||
|
||||
|
||||
int64_t AddVuSubscription(int64_t instanceId);
|
||||
void RemoveVuSubscription(int64_t subscriptionHandle);
|
||||
|
||||
|
||||
int64_t MonitorPort(int64_t instanceId,const std::string &key,float updateInterval, PortMonitorCallback onUpdate);
|
||||
void UnmonitorPort(int64_t subscriptionHandle);
|
||||
|
||||
void GetLv2Parameter(
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
std::function<void (const std::string&jsonResjult)> onSuccess,
|
||||
std::function<void (const std::string& error)> onError
|
||||
);
|
||||
|
||||
BankIndex GetBankIndex() const;
|
||||
void RenameBank(int64_t clientId,int64_t bankId, const std::string& newName);
|
||||
int64_t SaveBankAs(int64_t clientId,int64_t bankId, const std::string& newName);
|
||||
void OpenBank(int64_t clientId,int64_t bankId);
|
||||
|
||||
JackHostStatus GetJackStatus() {
|
||||
return this->jackHost->getJackStatus();
|
||||
}
|
||||
JackServerSettings GetJackServerSettings();
|
||||
void SetJackServerSettings(const JackServerSettings& jackServerSettings);
|
||||
|
||||
void ListenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
|
||||
void CancelListenForMidiEvent(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
void ListenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId);
|
||||
void CancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
const std::filesystem::path& GetWebRoot() const;
|
||||
|
||||
std::map<std::string,bool> GetFavorites() const;
|
||||
void SetFavorites(const std::map<std::string,bool> &favorites);
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
+16
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022-2023 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
|
||||
@@ -1235,6 +1235,17 @@ public:
|
||||
pReader->read(&favorites);
|
||||
this->model.SetFavorites(favorites);
|
||||
}
|
||||
else if (message == "setSystemMidiBindings")
|
||||
{
|
||||
std::vector<MidiBinding> bindings;
|
||||
pReader->read(&bindings);
|
||||
this->model.SetSystemMidiBindings(bindings);
|
||||
}
|
||||
else if (message == "getSystemMidiBindings")
|
||||
{
|
||||
std::vector<MidiBinding> bindings = this->model.GetSystemMidiBidings();
|
||||
this->Reply(replyTo,"getSystemMidiBindings",bindings);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error("Unknown message received: %s", message.c_str());
|
||||
@@ -1308,6 +1319,10 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) {
|
||||
Send("onSystemMidiBindingsChanged",bindings);
|
||||
}
|
||||
|
||||
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites)
|
||||
{
|
||||
Send("onFavoritesChanged", favorites);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BeastServer.hpp"
|
||||
#include "WebServer.hpp"
|
||||
#include "PiPedalModel.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022-2023 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
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include "Lv2Log.hpp"
|
||||
#include "VuUpdate.hpp"
|
||||
#include "JackHost.hpp"
|
||||
#include "AudioHost.hpp"
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
@@ -51,8 +51,25 @@ namespace pipedal
|
||||
|
||||
AtomOutput = 17,
|
||||
|
||||
MidiProgramChange = 18, // program change requested via midi.
|
||||
AckMidiProgramChange = 19,
|
||||
|
||||
NextMidiProgram = 20,
|
||||
|
||||
};
|
||||
|
||||
struct RealtimeNextMidiProgramRequest {
|
||||
int64_t requestId;
|
||||
int32_t direction;
|
||||
};
|
||||
|
||||
struct RealtimeMidiProgramRequest {
|
||||
int64_t requestId;
|
||||
int8_t bank;
|
||||
uint8_t program;
|
||||
};
|
||||
|
||||
|
||||
class RealtimeMonitorPortSubscription
|
||||
{
|
||||
public:
|
||||
@@ -304,6 +321,25 @@ namespace pipedal
|
||||
write(RingBufferCommand::OnMidiListen, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify host of a midi program change request.
|
||||
*
|
||||
* @param bank MIDI bank number, or -1 for current bank.
|
||||
* @param program MIDI program number.
|
||||
*/
|
||||
void OnMidiProgramChange(int64_t _requestId,int8_t _bank,uint8_t _program)
|
||||
{
|
||||
RealtimeMidiProgramRequest msg { requestId:_requestId, bank: _bank,program: _program};
|
||||
|
||||
|
||||
write(RingBufferCommand::MidiProgramChange,msg);
|
||||
}
|
||||
void OnNextMidiProgram(int64_t requestId,int32_t direction)
|
||||
{
|
||||
RealtimeNextMidiProgramRequest msg { requestId: requestId, direction:direction};
|
||||
write(RingBufferCommand::NextMidiProgram,msg);
|
||||
}
|
||||
|
||||
void SetControlValue(int effectIndex, int controlIndex, float value)
|
||||
{
|
||||
SetControlValueBody body;
|
||||
@@ -351,6 +387,10 @@ namespace pipedal
|
||||
{
|
||||
write(RingBufferCommand::SetVuSubscriptions, configuration);
|
||||
}
|
||||
void AckMidiProgramRequest(int64_t requestId)
|
||||
{
|
||||
write(RingBufferCommand::AckMidiProgramChange,requestId);
|
||||
}
|
||||
|
||||
void SetMonitorPortSubscriptions(RealtimeMonitorPortSubscriptions *subscriptions)
|
||||
{
|
||||
|
||||
+52
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2022-2023 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
|
||||
@@ -511,6 +511,7 @@ void Storage::SetPresetIndex(const PresetIndex &presets)
|
||||
this->currentBank = std::move(bankFile); // deleting any stray presets while we're at it.
|
||||
this->SaveCurrentBank();
|
||||
}
|
||||
|
||||
void Storage::GetPresetIndex(PresetIndex *pResult)
|
||||
{
|
||||
*pResult = PresetIndex();
|
||||
@@ -523,6 +524,15 @@ void Storage::GetPresetIndex(PresetIndex *pResult)
|
||||
pResult->presets().push_back(entry);
|
||||
}
|
||||
}
|
||||
int64_t Storage::GetPresetByProgramNumber(uint8_t program)const
|
||||
{
|
||||
if (program >= currentBank.presets().size())
|
||||
{
|
||||
if (currentBank.presets().size() == 0) return -1;
|
||||
program = (uint8_t)currentBank.presets().size()-1;
|
||||
}
|
||||
return currentBank.presets()[program]->instanceId();
|
||||
}
|
||||
|
||||
PedalBoard Storage::GetPreset(int64_t instanceId) const
|
||||
{
|
||||
@@ -736,6 +746,17 @@ void Storage::MoveBank(int from, int to)
|
||||
this->bankIndex.move(from, to);
|
||||
this->SaveBankIndex();
|
||||
}
|
||||
|
||||
|
||||
int64_t Storage::GetBankByMidiBankNumber(uint8_t bankNumber) {
|
||||
auto &entries = this->bankIndex.entries();
|
||||
if (bankNumber >= entries.size())
|
||||
{
|
||||
if (entries.size() == 0) return -1;
|
||||
bankNumber = (uint8_t)(entries.size()-1);
|
||||
}
|
||||
return entries[bankNumber].instanceId();
|
||||
}
|
||||
int64_t Storage::DeleteBank(int64_t bankId)
|
||||
{
|
||||
auto &entries = this->bankIndex.entries();
|
||||
@@ -1333,6 +1354,36 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
|
||||
#endif
|
||||
}
|
||||
|
||||
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding>&bindings)
|
||||
{
|
||||
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
|
||||
std::ofstream f;
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_writer writer(f);
|
||||
writer.write(bindings);
|
||||
}
|
||||
}
|
||||
std::vector<MidiBinding> Storage::GetSystemMidiBindings()
|
||||
{
|
||||
std::vector<MidiBinding> result;
|
||||
|
||||
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
|
||||
std::ifstream f;
|
||||
f.open(fileName);
|
||||
if (f.is_open())
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
} else {
|
||||
result.push_back(MidiBinding::SystemBinding("prevProgram"));
|
||||
result.push_back(MidiBinding::SystemBinding("nextProgram"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UserSettings)
|
||||
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
||||
|
||||
+5
-1
@@ -109,6 +109,7 @@ public:
|
||||
void LoadUserSettings();
|
||||
void SaveUserSettings();
|
||||
void LoadBank(int64_t instanceId);
|
||||
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
|
||||
const PedalBoard& GetCurrentPreset();
|
||||
void SaveCurrentPreset(const PedalBoard&pedalBoard);
|
||||
int64_t SaveCurrentPresetAs(const PedalBoard&pedalBoard, const std::string&namne,int64_t saveAfterInstanceId = -1);
|
||||
@@ -116,11 +117,12 @@ public:
|
||||
void GetPresetIndex(PresetIndex*pResult);
|
||||
void SetPresetIndex(const PresetIndex &presetIndex);
|
||||
PedalBoard GetPreset(int64_t instanceId) const;
|
||||
int64_t GetPresetByProgramNumber(uint8_t program) const;
|
||||
void GetBankFile(int64_t instanceId,BankFile*pResult) const;
|
||||
int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter);
|
||||
int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter);
|
||||
|
||||
|
||||
|
||||
bool LoadPreset(int64_t presetId);
|
||||
int64_t DeletePreset(int64_t presetId);
|
||||
bool RenamePreset(int64_t presetId, const std::string&name);
|
||||
@@ -170,6 +172,8 @@ public:
|
||||
|
||||
void SetShowStatusMonitor(bool show);
|
||||
bool GetShowStatusMonitor() const;
|
||||
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
|
||||
std::vector<MidiBinding> GetSystemMidiBindings();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <strings.h>
|
||||
#include "Ipv6Helpers.hpp"
|
||||
|
||||
#include "BeastServer.hpp"
|
||||
#include "WebServer.hpp"
|
||||
|
||||
#include "Uri.hpp"
|
||||
|
||||
@@ -133,7 +133,7 @@ std::string GetFromAddress(const tcp::socket &socket)
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
class BeastServerImpl : public BeastServer
|
||||
class WebServerImpl : public WebServer
|
||||
{
|
||||
private:
|
||||
int signalOnDone = -1;
|
||||
@@ -200,7 +200,7 @@ namespace pipedal
|
||||
|
||||
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession>, public SocketHandler::IWriteCallback
|
||||
{
|
||||
BeastServerImpl *pServer;
|
||||
WebServerImpl *pServer;
|
||||
server::connection_ptr webSocket;
|
||||
std::string fromAddress;
|
||||
std::shared_ptr<SocketHandler> socketHandler;
|
||||
@@ -224,7 +224,7 @@ namespace pipedal
|
||||
Lv2Log::info("WebSocketSession closed.");
|
||||
}
|
||||
using ptr = std::shared_ptr<WebSocketSession>;
|
||||
WebSocketSession(BeastServerImpl *pServer, server::connection_ptr &webSocket)
|
||||
WebSocketSession(WebServerImpl *pServer, server::connection_ptr &webSocket)
|
||||
: pServer(pServer),
|
||||
webSocket(webSocket)
|
||||
{
|
||||
@@ -599,9 +599,9 @@ namespace pipedal
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
|
||||
m_endpoint.set_open_handler(bind(&BeastServerImpl::on_open, this, _1));
|
||||
m_endpoint.set_close_handler(bind(&BeastServerImpl::on_close, this, _1));
|
||||
m_endpoint.set_http_handler(bind(&BeastServerImpl::on_http, this, _1));
|
||||
m_endpoint.set_open_handler(bind(&WebServerImpl::on_open, this, _1));
|
||||
m_endpoint.set_close_handler(bind(&WebServerImpl::on_close, this, _1));
|
||||
m_endpoint.set_http_handler(bind(&WebServerImpl::on_http, this, _1));
|
||||
|
||||
std::string hostName = getHostName();
|
||||
if (hostName.length() != 0)
|
||||
@@ -660,7 +660,7 @@ namespace pipedal
|
||||
}
|
||||
}
|
||||
|
||||
static void ThreadProc(BeastServerImpl *server)
|
||||
static void ThreadProc(WebServerImpl *server)
|
||||
{
|
||||
server->Run();
|
||||
}
|
||||
@@ -717,7 +717,7 @@ namespace pipedal
|
||||
this->pBgThread = new std::thread(ThreadProc, this);
|
||||
}
|
||||
|
||||
BeastServerImpl(const std::string &address, int port, const char *rootPath, int threads)
|
||||
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads)
|
||||
: address(address),
|
||||
rootPath(rootPath),
|
||||
port(port),
|
||||
@@ -727,7 +727,7 @@ namespace pipedal
|
||||
};
|
||||
} // namespace pipedal
|
||||
|
||||
std::shared_ptr<ISocketFactory> BeastServerImpl::GetSocketFactory(const uri &requestUri)
|
||||
std::shared_ptr<ISocketFactory> WebServerImpl::GetSocketFactory(const uri &requestUri)
|
||||
{
|
||||
|
||||
for (auto factory : this->socket_factories)
|
||||
@@ -740,7 +740,7 @@ std::shared_ptr<ISocketFactory> BeastServerImpl::GetSocketFactory(const uri &req
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<BeastServer> pipedal::createBeastServer(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
|
||||
std::shared_ptr<WebServer> pipedal::WebServer::create(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
|
||||
{
|
||||
return std::shared_ptr<BeastServer>(new BeastServerImpl(address.to_string(), port, rootPath, threads));
|
||||
return std::shared_ptr<WebServer>(new WebServerImpl(address.to_string(), port, rootPath, threads));
|
||||
}
|
||||
@@ -68,10 +68,10 @@ public:
|
||||
//xxx move this to HtmlHelpers.
|
||||
std::string last_modified(const std::filesystem::path& path);
|
||||
|
||||
class BeastServerImpl;
|
||||
class WebServerImpl;
|
||||
|
||||
class SocketHandler {
|
||||
friend class BeastServerImpl;
|
||||
friend class WebServerImpl;
|
||||
public:
|
||||
|
||||
class IWriteCallback {
|
||||
@@ -213,9 +213,9 @@ public:
|
||||
|
||||
};
|
||||
|
||||
class BeastServer {
|
||||
class WebServer {
|
||||
public:
|
||||
virtual ~BeastServer() { }
|
||||
virtual ~WebServer() { }
|
||||
|
||||
virtual void SetLogHttpRequests(bool enableLogging) = 0;
|
||||
|
||||
@@ -227,10 +227,17 @@ public:
|
||||
|
||||
// signalOnDone: fire the specified POSIX signal when the service thread terminates. -1 for no signal.
|
||||
virtual void RunInBackground(int signalOnDone = -1) = 0;
|
||||
|
||||
static std::shared_ptr<WebServer> create(
|
||||
const boost::asio::ip::address &address,
|
||||
int port,
|
||||
const char *rootPath,
|
||||
int threads);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
std::shared_ptr<BeastServer> createBeastServer(const boost::asio::ip::address &address, int port, const char *rootPath, int threads);
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
#include "pch.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
#include "BeastServer.hpp"
|
||||
#include "WebServer.hpp"
|
||||
#include "MemDebug.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
|
||||
TEST_CASE("WebServer shutdown", "[webServerShutdown][Build][Dev]")
|
||||
{
|
||||
MemStats initialMemory = GetMemStats();
|
||||
{
|
||||
@@ -34,7 +34,7 @@ TEST_CASE("BeastServer shutdown", "[beastServerShutdown][Build][Dev]")
|
||||
std::string doc_root = ".";
|
||||
auto const threads = 3;
|
||||
|
||||
auto server = createBeastServer(
|
||||
auto server = WebServer::create(
|
||||
address, port, doc_root.c_str(), threads);
|
||||
server->RunInBackground();
|
||||
sleep(5);
|
||||
+3
-3
@@ -20,7 +20,7 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "AudioConfig.hpp"
|
||||
#include "BeastServer.hpp"
|
||||
#include "WebServer.hpp"
|
||||
#include <iostream>
|
||||
#include "Lv2Log.hpp"
|
||||
#include "ServiceConfiguration.hpp"
|
||||
@@ -601,7 +601,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
uint16_t port;
|
||||
std::shared_ptr<BeastServer> server;
|
||||
std::shared_ptr<WebServer> server;
|
||||
try
|
||||
{
|
||||
auto const address = boost::asio::ip::make_address(configuration.GetSocketServerAddress());
|
||||
@@ -609,7 +609,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
auto const threads = std::max<int>(1, configuration.GetThreads());
|
||||
|
||||
server = createBeastServer(
|
||||
server = WebServer::create(
|
||||
address, port, web_root.c_str(), threads);
|
||||
|
||||
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)threads);
|
||||
|
||||
Reference in New Issue
Block a user