1518 lines
46 KiB
C++
1518 lines
46 KiB
C++
// 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 "PiPedalSocket.hpp"
|
|
#include "json.hpp"
|
|
#include "viewstream.hpp"
|
|
#include "PiPedalVersion.hpp"
|
|
#include <atomic>
|
|
#include "Lv2Log.hpp"
|
|
#include "JackConfiguration.hpp"
|
|
#include <future>
|
|
#include <atomic>
|
|
|
|
#include "ShutdownClient.hpp"
|
|
#include "WifiConfigSettings.hpp"
|
|
#include "WifiChannels.hpp"
|
|
#include "SysExec.hpp"
|
|
#include "PiPedalAlsa.hpp"
|
|
#include <filesystem>
|
|
|
|
using namespace std;
|
|
using namespace pipedal;
|
|
|
|
|
|
class NotifyMidiListenerBody {
|
|
public:
|
|
int64_t clientHandle_;
|
|
bool isNote_;
|
|
int32_t noteOrControl_;
|
|
DECLARE_JSON_MAP(NotifyMidiListenerBody);
|
|
|
|
};
|
|
JSON_MAP_BEGIN(NotifyMidiListenerBody)
|
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, clientHandle)
|
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, isNote)
|
|
JSON_MAP_REFERENCE(NotifyMidiListenerBody, noteOrControl)
|
|
JSON_MAP_END()
|
|
|
|
class NotifyAtomOutputBody {
|
|
public:
|
|
int64_t clientHandle_;
|
|
uint64_t instanceId_;
|
|
|
|
std::string atomJson_;
|
|
|
|
DECLARE_JSON_MAP(NotifyAtomOutputBody);
|
|
};
|
|
JSON_MAP_BEGIN(NotifyAtomOutputBody)
|
|
JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle)
|
|
JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId)
|
|
JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson)
|
|
JSON_MAP_END()
|
|
|
|
class ListenForMidiEventBody {
|
|
public:
|
|
bool listenForControlsOnly_;
|
|
int64_t handle_;
|
|
DECLARE_JSON_MAP(ListenForMidiEventBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(ListenForMidiEventBody)
|
|
JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly)
|
|
JSON_MAP_REFERENCE(ListenForMidiEventBody, handle)
|
|
JSON_MAP_END()
|
|
|
|
class ListenForAtomOutputBody {
|
|
public:
|
|
uint64_t instanceId_;
|
|
int64_t handle_;
|
|
DECLARE_JSON_MAP(ListenForAtomOutputBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(ListenForAtomOutputBody)
|
|
JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId)
|
|
JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle)
|
|
JSON_MAP_END()
|
|
|
|
|
|
class OnLoadPluginPresetBody {
|
|
public:
|
|
int64_t instanceId_;
|
|
std::vector<ControlValue> controlValues_;
|
|
DECLARE_JSON_MAP(OnLoadPluginPresetBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(OnLoadPluginPresetBody)
|
|
JSON_MAP_REFERENCE(OnLoadPluginPresetBody, instanceId)
|
|
JSON_MAP_REFERENCE(OnLoadPluginPresetBody, controlValues)
|
|
JSON_MAP_END()
|
|
|
|
class LoadPluginPresetBody {
|
|
public:
|
|
uint64_t pluginInstanceId_;
|
|
uint64_t presetInstanceId_;
|
|
DECLARE_JSON_MAP(LoadPluginPresetBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(LoadPluginPresetBody)
|
|
JSON_MAP_REFERENCE(LoadPluginPresetBody, pluginInstanceId)
|
|
JSON_MAP_REFERENCE(LoadPluginPresetBody, presetInstanceId)
|
|
JSON_MAP_END()
|
|
|
|
class FromToBody
|
|
{
|
|
public:
|
|
int64_t from_ = -1;
|
|
int64_t to_ = -1;
|
|
|
|
DECLARE_JSON_MAP(FromToBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(FromToBody)
|
|
JSON_MAP_REFERENCE(FromToBody, from)
|
|
JSON_MAP_REFERENCE(FromToBody, to)
|
|
JSON_MAP_END()
|
|
|
|
class MonitorResultBody
|
|
{
|
|
public:
|
|
int64_t subscriptionHandle_ = -1;
|
|
float value_;
|
|
|
|
DECLARE_JSON_MAP(MonitorResultBody);
|
|
};
|
|
JSON_MAP_BEGIN(MonitorResultBody)
|
|
JSON_MAP_REFERENCE(MonitorResultBody, subscriptionHandle)
|
|
JSON_MAP_REFERENCE(MonitorResultBody, value)
|
|
JSON_MAP_END()
|
|
|
|
|
|
class GetLv2ParameterBody
|
|
{
|
|
public:
|
|
int64_t instanceId_;
|
|
std::string uri_;
|
|
|
|
DECLARE_JSON_MAP(GetLv2ParameterBody);
|
|
};
|
|
JSON_MAP_BEGIN(GetLv2ParameterBody)
|
|
JSON_MAP_REFERENCE(GetLv2ParameterBody, instanceId)
|
|
JSON_MAP_REFERENCE(GetLv2ParameterBody, uri)
|
|
JSON_MAP_END()
|
|
|
|
class MonitorPortBody
|
|
{
|
|
public:
|
|
int64_t instanceId_ = -1;
|
|
std::string key_;
|
|
float_t updateRate_ = 0;
|
|
|
|
DECLARE_JSON_MAP(MonitorPortBody);
|
|
};
|
|
JSON_MAP_BEGIN(MonitorPortBody)
|
|
JSON_MAP_REFERENCE(MonitorPortBody, instanceId)
|
|
JSON_MAP_REFERENCE(MonitorPortBody, key)
|
|
JSON_MAP_REFERENCE(MonitorPortBody, updateRate)
|
|
JSON_MAP_END()
|
|
|
|
class SaveCurrentPresetAsBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
std::string name_;
|
|
int64_t saveAfterInstanceId_ = -1;
|
|
|
|
DECLARE_JSON_MAP(SaveCurrentPresetAsBody);
|
|
};
|
|
JSON_MAP_BEGIN(SaveCurrentPresetAsBody)
|
|
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, clientId)
|
|
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, name)
|
|
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, saveAfterInstanceId)
|
|
JSON_MAP_END()
|
|
|
|
class SavePluginPresetAsBody
|
|
{
|
|
public:
|
|
int64_t instanceId_ = -1;
|
|
std::string name_;
|
|
|
|
DECLARE_JSON_MAP(SavePluginPresetAsBody);
|
|
};
|
|
JSON_MAP_BEGIN(SavePluginPresetAsBody)
|
|
JSON_MAP_REFERENCE(SavePluginPresetAsBody, instanceId)
|
|
JSON_MAP_REFERENCE(SavePluginPresetAsBody, name)
|
|
JSON_MAP_END()
|
|
|
|
class RenameBankBody {
|
|
public:
|
|
int64_t bankId_;
|
|
std::string newName_;
|
|
DECLARE_JSON_MAP(RenameBankBody);
|
|
|
|
};
|
|
JSON_MAP_BEGIN(RenameBankBody)
|
|
JSON_MAP_REFERENCE(RenameBankBody,bankId)
|
|
JSON_MAP_REFERENCE(RenameBankBody, newName)
|
|
JSON_MAP_END()
|
|
|
|
class RenamePresetBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
int64_t instanceId_ = -1;
|
|
std::string name_;
|
|
|
|
DECLARE_JSON_MAP(RenamePresetBody);
|
|
};
|
|
JSON_MAP_BEGIN(RenamePresetBody)
|
|
JSON_MAP_REFERENCE(RenamePresetBody, clientId)
|
|
JSON_MAP_REFERENCE(RenamePresetBody, instanceId)
|
|
JSON_MAP_REFERENCE(RenamePresetBody, name)
|
|
JSON_MAP_END()
|
|
|
|
class CopyPresetBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
int64_t fromId_ = -1;
|
|
int64_t toId_ = -1;
|
|
|
|
DECLARE_JSON_MAP(CopyPresetBody);
|
|
};
|
|
JSON_MAP_BEGIN(CopyPresetBody)
|
|
JSON_MAP_REFERENCE(CopyPresetBody, clientId)
|
|
JSON_MAP_REFERENCE(CopyPresetBody, fromId)
|
|
JSON_MAP_REFERENCE(CopyPresetBody, toId)
|
|
JSON_MAP_END()
|
|
|
|
class CopyPluginPresetBody
|
|
{
|
|
public:
|
|
std::string pluginUri_;
|
|
uint64_t instanceId_;
|
|
|
|
DECLARE_JSON_MAP(CopyPluginPresetBody);
|
|
};
|
|
JSON_MAP_BEGIN(CopyPluginPresetBody)
|
|
JSON_MAP_REFERENCE(CopyPluginPresetBody, pluginUri)
|
|
JSON_MAP_REFERENCE(CopyPluginPresetBody, instanceId)
|
|
JSON_MAP_END()
|
|
|
|
class PedalBoardItemEnabledBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
int64_t instanceId_ = -1;
|
|
bool enabled_ = true;
|
|
|
|
DECLARE_JSON_MAP(PedalBoardItemEnabledBody);
|
|
};
|
|
JSON_MAP_BEGIN(PedalBoardItemEnabledBody)
|
|
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, clientId)
|
|
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId)
|
|
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled)
|
|
JSON_MAP_END()
|
|
|
|
class SetCurrentPedalBoardBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
PedalBoard pedalBoard_;
|
|
|
|
DECLARE_JSON_MAP(SetCurrentPedalBoardBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(SetCurrentPedalBoardBody)
|
|
JSON_MAP_REFERENCE(SetCurrentPedalBoardBody, clientId)
|
|
JSON_MAP_REFERENCE(SetCurrentPedalBoardBody, pedalBoard)
|
|
JSON_MAP_END()
|
|
|
|
class ChannelSelectionChangedBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
JackChannelSelection *jackChannelSelection_ = nullptr;
|
|
|
|
DECLARE_JSON_MAP(ChannelSelectionChangedBody);
|
|
};
|
|
JSON_MAP_BEGIN(ChannelSelectionChangedBody)
|
|
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, clientId)
|
|
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, jackChannelSelection)
|
|
JSON_MAP_END()
|
|
|
|
class PresetsChangedBody
|
|
{
|
|
public:
|
|
int64_t clientId_ = -1;
|
|
PresetIndex *presets_ = nullptr;
|
|
|
|
DECLARE_JSON_MAP(PresetsChangedBody);
|
|
};
|
|
JSON_MAP_BEGIN(PresetsChangedBody)
|
|
JSON_MAP_REFERENCE(PresetsChangedBody, clientId)
|
|
JSON_MAP_REFERENCE(PresetsChangedBody, presets)
|
|
JSON_MAP_END()
|
|
|
|
class ControlChangedBody
|
|
{
|
|
public:
|
|
int64_t clientId_;
|
|
int64_t instanceId_;
|
|
std::string symbol_;
|
|
float value_;
|
|
|
|
DECLARE_JSON_MAP(ControlChangedBody);
|
|
};
|
|
|
|
JSON_MAP_BEGIN(ControlChangedBody)
|
|
JSON_MAP_REFERENCE(ControlChangedBody, clientId)
|
|
JSON_MAP_REFERENCE(ControlChangedBody, instanceId)
|
|
JSON_MAP_REFERENCE(ControlChangedBody, symbol)
|
|
JSON_MAP_REFERENCE(ControlChangedBody, value)
|
|
JSON_MAP_END()
|
|
|
|
|
|
|
|
|
|
static void requestShutdown(bool restart)
|
|
{
|
|
if (ShutdownClient::CanUseShutdownClient())
|
|
{
|
|
ShutdownClient::RequestShutdown(restart);
|
|
}
|
|
else {
|
|
// ONLY works when interactively logged in.
|
|
std::stringstream s;
|
|
s << "/usr/sbin/shutdown ";
|
|
if (restart)
|
|
{
|
|
s << "-r";
|
|
} else
|
|
{
|
|
s << "-P";
|
|
}
|
|
s << " now";
|
|
|
|
if (sysExec(s.str().c_str()) != EXIT_SUCCESS)
|
|
{
|
|
Lv2Log::error("shutdown failed.");
|
|
if (restart) {
|
|
throw new PiPedalStateException("Restart request failed.");
|
|
} else {
|
|
throw new PiPedalStateException("Shutdown request failed.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class PiPedalSocketHandler : public SocketHandler, public IPiPedalModelSubscriber
|
|
{
|
|
private:
|
|
std::recursive_mutex writeMutex;
|
|
PiPedalModel &model;
|
|
static std::atomic<uint64_t> nextClientId;
|
|
std::string imageList;
|
|
|
|
uint64_t clientId;
|
|
// pedalboard is mutable and not thread-safe.
|
|
std::recursive_mutex subscriptionMutex;
|
|
struct VuSubscription
|
|
{
|
|
int64_t subscriptionHandle;
|
|
int64_t instanceId;
|
|
};
|
|
std::vector<VuSubscription> activeVuSubscriptions;
|
|
|
|
struct PortMonitorSubscription
|
|
{
|
|
int64_t subscriptionHandle = -1;
|
|
int64_t instanceId = -1;
|
|
bool waitingForAck = false;
|
|
std::string key;
|
|
};
|
|
|
|
std::vector<PortMonitorSubscription> activePortMonitors;
|
|
|
|
public:
|
|
virtual int64_t GetClientId() { return clientId; }
|
|
|
|
virtual ~PiPedalSocketHandler()
|
|
{
|
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
|
{
|
|
model.UnmonitorPort(activePortMonitors[i].subscriptionHandle);
|
|
}
|
|
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
|
|
{
|
|
model.RemoveVuSubscription(activeVuSubscriptions[i].subscriptionHandle);
|
|
}
|
|
|
|
model.RemoveNotificationSubsription(this);
|
|
}
|
|
|
|
virtual void Close()
|
|
{
|
|
SocketHandler::Close();
|
|
}
|
|
|
|
PiPedalSocketHandler(PiPedalModel &model)
|
|
: model(model), clientId(++nextClientId)
|
|
{
|
|
std::stringstream imageList;
|
|
const std::filesystem::path& webRoot = model.GetWebRoot() / "img";
|
|
bool firstTime = true;
|
|
for (const auto&entry: std::filesystem::directory_iterator(webRoot))
|
|
{
|
|
if (!firstTime)
|
|
{
|
|
imageList << ";";
|
|
}
|
|
firstTime = false;
|
|
imageList << entry.path().filename().string();
|
|
}
|
|
this->imageList = imageList.str();
|
|
}
|
|
|
|
private:
|
|
void JsonReply(int replyTo, const char *message, const char *json)
|
|
{
|
|
std::stringstream s(ios_base::out);
|
|
|
|
json_writer writer(s, true);
|
|
|
|
writer.start_array();
|
|
{
|
|
writer.start_object();
|
|
{
|
|
if (replyTo != -1)
|
|
{
|
|
writer.write_member("reply", replyTo);
|
|
writer.write_raw(",");
|
|
}
|
|
writer.write_member("message", message);
|
|
}
|
|
writer.end_object();
|
|
writer.write_raw(",");
|
|
writer.write_raw(json);
|
|
}
|
|
writer.end_array();
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(this->writeMutex);
|
|
this->send(s.str());
|
|
}
|
|
}
|
|
// void JsonSend(const char *message, const char *json)
|
|
// {
|
|
// JsonReply(-1, message, json);
|
|
// }
|
|
template <typename T>
|
|
void Reply(int replyTo, const char *message, const T &value)
|
|
{
|
|
std::stringstream s(ios_base::out);
|
|
|
|
json_writer writer(s, true);
|
|
writer.start_array();
|
|
{
|
|
writer.start_object();
|
|
{
|
|
if (replyTo != -1)
|
|
{
|
|
writer.write_member("reply", replyTo);
|
|
writer.write_raw(",");
|
|
}
|
|
writer.write_member("message", message);
|
|
}
|
|
writer.end_object();
|
|
writer.write_raw(",");
|
|
writer.write(value);
|
|
}
|
|
writer.end_array();
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(this->writeMutex);
|
|
this->send(s.str());
|
|
}
|
|
}
|
|
void Reply(int replyTo, const char *message)
|
|
{
|
|
if (replyTo == -1)
|
|
return;
|
|
std::stringstream s(ios_base::out);
|
|
|
|
json_writer writer(s, true);
|
|
writer.start_array();
|
|
{
|
|
writer.start_object();
|
|
{
|
|
if (replyTo != -1)
|
|
{
|
|
writer.write_member("reply", replyTo);
|
|
writer.write_raw(",");
|
|
}
|
|
writer.write_member("message", message);
|
|
}
|
|
writer.end_object();
|
|
}
|
|
writer.end_array();
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(this->writeMutex);
|
|
this->send(s.str());
|
|
}
|
|
}
|
|
|
|
private:
|
|
class IRequestReservation
|
|
{
|
|
int reservationId;
|
|
|
|
public:
|
|
IRequestReservation(int reservationId)
|
|
: reservationId(reservationId)
|
|
{
|
|
}
|
|
int GetReservationid() const { return reservationId; }
|
|
virtual ~IRequestReservation() {}
|
|
virtual void onResult(json_reader *pReader) = 0;
|
|
virtual void onError(const std::exception &e) = 0;
|
|
};
|
|
|
|
class RequestReservationBase : public IRequestReservation
|
|
{
|
|
protected:
|
|
virtual void onResult(json_reader &reader) = 0;
|
|
};
|
|
template <typename REPLY>
|
|
class RequestReservation : public IRequestReservation
|
|
{
|
|
public:
|
|
using ResponseFn = std::function<void(const REPLY &)>;
|
|
using ErrorFn = std::function<void(const std::exception &)>;
|
|
|
|
private:
|
|
ResponseFn responseFn;
|
|
bool hasErrorFn;
|
|
ErrorFn errorFn;
|
|
|
|
virtual void onError(const std::exception &e)
|
|
{
|
|
if (hasErrorFn)
|
|
{
|
|
errorFn(e);
|
|
}
|
|
else
|
|
{
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
virtual void onResult(json_reader *pReader)
|
|
{
|
|
REPLY value;
|
|
try
|
|
{
|
|
if (pReader == nullptr)
|
|
{
|
|
throw PiPedalException("No value provide for reply.");
|
|
}
|
|
pReader->read(&value);
|
|
responseFn(value);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
onError(e);
|
|
}
|
|
};
|
|
|
|
public:
|
|
RequestReservation(int reservationId, ResponseFn onResponse, ErrorFn errorFn)
|
|
: IRequestReservation(reservationId), responseFn(onResponse), errorFn(errorFn), hasErrorFn(true)
|
|
{
|
|
}
|
|
RequestReservation(int reservationId, ResponseFn onResponse)
|
|
: IRequestReservation(reservationId), responseFn(onResponse), hasErrorFn(false)
|
|
{
|
|
}
|
|
};
|
|
std::recursive_mutex requestMutex;
|
|
std::vector<IRequestReservation *> requestReservations;
|
|
std::atomic<int> nextRequestId{1};
|
|
|
|
public:
|
|
template <typename REPLY, typename T>
|
|
void Request(const char *message, const T &body,
|
|
std::function<void(const REPLY &)> onSuccess,
|
|
std::function<void(const std::exception &error)> onError)
|
|
{
|
|
try
|
|
{
|
|
RequestReservation<REPLY> *reservation = new RequestReservation<REPLY>(
|
|
(int)++nextRequestId,
|
|
onSuccess,
|
|
onError);
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(requestMutex);
|
|
requestReservations.push_back(reservation);
|
|
}
|
|
std::stringstream s(ios_base::out);
|
|
|
|
json_writer writer(s, true);
|
|
writer.start_array();
|
|
{
|
|
writer.start_object();
|
|
{
|
|
writer.write_member("replyTo", reservation->GetReservationid());
|
|
writer.write_raw(",");
|
|
writer.write_member("message", message);
|
|
}
|
|
writer.end_object();
|
|
writer.write_raw(",");
|
|
writer.write(body);
|
|
}
|
|
writer.end_array();
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(this->writeMutex);
|
|
this->send(s.str());
|
|
}
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
onError(PiPedalException(e.what()));
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void Send(const char *message, const T &body)
|
|
{
|
|
Reply(-1, message, body);
|
|
}
|
|
void Send(const char *message)
|
|
{
|
|
Reply(-1, message);
|
|
}
|
|
|
|
void SendError(int replyTo, std::exception &e)
|
|
{
|
|
Reply(replyTo, "error", e.what());
|
|
}
|
|
void SendError(int replyTo, const std::string &what)
|
|
{
|
|
Reply(replyTo, "error", what);
|
|
}
|
|
|
|
bool waitingForPortMonitorAck(uint64_t subscriptionId)
|
|
{
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
|
{
|
|
auto &portMonitor = activePortMonitors[i];
|
|
if (portMonitor.subscriptionHandle == subscriptionId)
|
|
{
|
|
if (portMonitor.waitingForAck)
|
|
return true;
|
|
portMonitor.waitingForAck = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true; // subscription has been closed. don't sent to client.
|
|
}
|
|
void portMonitorAck(uint64_t subscriptionId)
|
|
{
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
for (int i = 0; i < this->activePortMonitors.size(); ++i)
|
|
{
|
|
auto &portMonitor = activePortMonitors[i];
|
|
if (portMonitor.subscriptionHandle == subscriptionId)
|
|
{
|
|
portMonitor.waitingForAck = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleMessage(int reply, int replyTo, const std::string &message, json_reader *pReader)
|
|
{
|
|
if (reply != -1)
|
|
{
|
|
IRequestReservation *reservation = nullptr;
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(this->requestMutex);
|
|
for (auto i = this->requestReservations.begin(); i != this->requestReservations.end(); ++i)
|
|
{
|
|
if ((*i)->GetReservationid() == reply)
|
|
{
|
|
reservation = (*i);
|
|
requestReservations.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// be careful not to take down the whole server because of a client that's shutting down.
|
|
if (reservation != nullptr)
|
|
{
|
|
try
|
|
{
|
|
reservation->onResult(pReader);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
Lv2Log::error("Socket: Invalid reply '%s'. (%s)", message.c_str(), e.what());
|
|
}
|
|
delete reservation;
|
|
}
|
|
else
|
|
{
|
|
Lv2Log::warning("Socket: Reply '%s' with nobody waiting.", message.c_str());
|
|
}
|
|
return;
|
|
}
|
|
if (message == "previewControl")
|
|
{
|
|
ControlChangedBody message;
|
|
pReader->read(&message);
|
|
this->model.PreviewControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
|
}
|
|
else if (message == "setControl")
|
|
{
|
|
ControlChangedBody message;
|
|
pReader->read(&message);
|
|
this->model.SetControl(message.clientId_, message.instanceId_, message.symbol_, message.value_);
|
|
}
|
|
else if (message == "listenForMidiEvent")
|
|
{
|
|
ListenForMidiEventBody body;
|
|
pReader->read(&body);
|
|
this->model.ListenForMidiEvent(this->clientId,body.handle_, body.listenForControlsOnly_);
|
|
}
|
|
else if (message == "cancelListenForMidiEvent")
|
|
{
|
|
uint64_t handle;
|
|
pReader->read(&handle);
|
|
this->model.CancelListenForMidiEvent(this->clientId,handle);
|
|
}
|
|
else if (message == "listenForAtomOutput")
|
|
{
|
|
ListenForAtomOutputBody body;
|
|
pReader->read(&body);
|
|
this->model.ListenForAtomOutputs(this->clientId,body.handle_, body.instanceId_);
|
|
}
|
|
else if (message == "cancelListenForAtomOutput")
|
|
{
|
|
int64_t handle;
|
|
pReader->read(&handle);
|
|
this->model.CancelListenForMidiEvent(this->clientId,handle);
|
|
}
|
|
|
|
else if (message == "getJackStatus")
|
|
{
|
|
JackHostStatus status = model.GetJackStatus();
|
|
this->Reply(replyTo,"getJackStatus",status);
|
|
} else if (message == "getAlsaDevices")
|
|
{
|
|
std::vector<AlsaDeviceInfo> devices = model.GetAlsaDevices();
|
|
this->Reply(replyTo,"getAlsaDevices",devices);
|
|
|
|
|
|
} else if (message == "getWifiChannels")
|
|
{
|
|
std::string country;
|
|
pReader->read(&country);
|
|
std::vector<WifiChannel> channels = pipedal::getWifiChannels(country.c_str());
|
|
this->Reply(replyTo,"getWifiChannels",channels);
|
|
}
|
|
else if (message == "getPluginPresets")
|
|
{
|
|
std::string uri;
|
|
pReader->read(&uri);
|
|
this->Reply(replyTo,"getPluginPresets",this->model.GetPluginUiPresets(uri));
|
|
}
|
|
else if (message == "loadPluginPreset")
|
|
{
|
|
LoadPluginPresetBody body;
|
|
pReader->read(&body);
|
|
this->model.LoadPluginPreset(body.pluginInstanceId_,body.presetInstanceId_);
|
|
|
|
}
|
|
else if (message == "setJackServerSettings") {
|
|
JackServerSettings jackServerSettings;
|
|
pReader->read(&jackServerSettings);
|
|
this->model.SetJackServerSettings(jackServerSettings);
|
|
this->Reply(replyTo,"setJackserverSettings");
|
|
|
|
}
|
|
else if (message == "setWifiConfigSettings") {
|
|
WifiConfigSettings wifiConfigSettings;
|
|
pReader->read(&wifiConfigSettings);
|
|
if (!ShutdownClient::CanUseShutdownClient())
|
|
{
|
|
throw PiPedalException("Can't change server settings when running interactively.");
|
|
}
|
|
std::string fromAddress = this->getFromAddress();
|
|
if (!ShutdownClient::IsOnLocalSubnet(fromAddress))
|
|
{
|
|
throw PiPedalException("Permission denied. Not on local subnet.");
|
|
}
|
|
|
|
|
|
this->model.SetWifiConfigSettings(wifiConfigSettings);
|
|
this->Reply(replyTo,"setWifiConfigSettings");
|
|
}
|
|
else if (message == "getWifiConfigSettings") {
|
|
this->Reply(replyTo, "getWifiConfigSettings", model.GetWifiConfigSettings());
|
|
|
|
}
|
|
|
|
else if (message == "getJackServerSettings") {
|
|
this->Reply(replyTo, "getJackServerSettings", model.GetJackServerSettings());
|
|
|
|
}
|
|
else if (message == "getBankIndex") {
|
|
BankIndex bankIndex = model.GetBankIndex();
|
|
this->Reply(replyTo, "getBankIndex", bankIndex);
|
|
|
|
}
|
|
else if (message == "getJackConfiguration")
|
|
{
|
|
JackConfiguration configuration = this->model.GetJackConfiguration();
|
|
this->Reply(replyTo, "getJackConfiguration", configuration);
|
|
}
|
|
else if (message == "getJackSettings")
|
|
{
|
|
JackChannelSelection selection = this->model.GetJackChannelSelection();
|
|
this->Reply(replyTo, "getJackSettings", selection);
|
|
}
|
|
else if (message == "saveCurrentPreset")
|
|
{
|
|
this->model.SaveCurrentPreset(this->clientId);
|
|
}
|
|
else if (message == "saveCurrentPresetAs")
|
|
{
|
|
SaveCurrentPresetAsBody body;
|
|
pReader->read(&body);
|
|
int64_t result = this->model.SaveCurrentPresetAs(this->clientId, body.name_, body.saveAfterInstanceId_);
|
|
Reply(replyTo, "saveCurrentPresetsAs", result);
|
|
}
|
|
else if (message == "savePluginPresetAs")
|
|
{
|
|
SavePluginPresetAsBody body;
|
|
pReader->read(&body);
|
|
int64_t result = this->model.SavePluginPresetAs(body.instanceId_, body.name_);
|
|
Reply(replyTo, "saveCurrentPresetsAs", result);
|
|
}
|
|
else if (message == "getPresets")
|
|
{
|
|
PresetIndex presets;
|
|
this->model.GetPresets(&presets);
|
|
Reply(replyTo, "getPresets", presets);
|
|
}
|
|
else if (message == "setPedalBoardItemEnable")
|
|
{
|
|
PedalBoardItemEnabledBody body;
|
|
pReader->read(&body);
|
|
model.SetPedalBoardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
|
|
}
|
|
else if (message == "setCurrentPedalBoard")
|
|
{
|
|
{
|
|
SetCurrentPedalBoardBody body;
|
|
|
|
pReader->read(&body);
|
|
this->model.SetPedalBoard(body.clientId_, body.pedalBoard_);
|
|
}
|
|
}
|
|
else if (message == "currentPedalBoard")
|
|
{
|
|
auto pedalBoard = model.GetCurrentPedalBoardCopy();
|
|
Reply(replyTo, "currentPedalBoard", pedalBoard);
|
|
}
|
|
else if (message == "plugins")
|
|
{
|
|
auto ui_plugins = model.GetLv2Host().GetUiPlugins();
|
|
Reply(replyTo, "plugins", ui_plugins);
|
|
}
|
|
else if (message == "pluginClasses")
|
|
{
|
|
auto classes = model.GetLv2Host().GetLv2PluginClass();
|
|
Reply(replyTo, "pluginClasses", classes);
|
|
}
|
|
else if (message == "hello")
|
|
{
|
|
this->model.AddNotificationSubscription(this);
|
|
Reply(replyTo, "ehlo", clientId);
|
|
}
|
|
else if (message == "setJackSettings")
|
|
{
|
|
JackChannelSelection jackSettings;
|
|
pReader->read(&jackSettings);
|
|
this->model.SetJackChannelSelection(this->clientId, jackSettings);
|
|
}
|
|
else if (message == "version")
|
|
{
|
|
PiPedalVersion version;
|
|
|
|
Reply(replyTo, "version", version);
|
|
}
|
|
else if (message == "loadPreset")
|
|
{
|
|
int64_t instanceId = 0;
|
|
pReader->read(&instanceId);
|
|
model.LoadPreset(this->clientId, instanceId);
|
|
}
|
|
else if (message == "updatePresets")
|
|
{
|
|
PresetIndex newIndex;
|
|
pReader->read(&newIndex);
|
|
bool result = model.UpdatePresets(this->clientId, newIndex);
|
|
this->Reply(replyTo, "updatePresets", result);
|
|
}
|
|
else if (message == "updatePluginPresets")
|
|
{
|
|
PluginUiPresets pluginPresets;
|
|
pReader->read(&pluginPresets);
|
|
model.UpdatePluginPresets(pluginPresets);
|
|
this->Reply(replyTo, "updatePluginPresets", true);
|
|
}
|
|
else if (message == "moveBank")
|
|
{
|
|
FromToBody body;
|
|
pReader->read(&body);
|
|
model.MoveBank(this->clientId, body.from_, body.to_);
|
|
this->Reply(replyTo, "moveBank");
|
|
}
|
|
else if (message == "shutdown")
|
|
{
|
|
PresetIndex newIndex;
|
|
|
|
requestShutdown(false);
|
|
this->Reply(replyTo,"shutdown");
|
|
}
|
|
else if (message == "restart")
|
|
{
|
|
PresetIndex newIndex;
|
|
requestShutdown(true);
|
|
this->Reply(replyTo,"restart");
|
|
}
|
|
else if (message == "deletePresetItem")
|
|
{
|
|
int64_t instanceId = 0;
|
|
pReader->read(&instanceId);
|
|
int64_t result = model.DeletePreset(this->clientId, instanceId);
|
|
this->Reply(replyTo, "deletePresetItem", result);
|
|
}
|
|
else if (message == "deleteBankItem")
|
|
{
|
|
int64_t instanceId = 0;
|
|
pReader->read(&instanceId);
|
|
uint64_t result = model.DeleteBank(this->clientId, instanceId);
|
|
this->Reply(replyTo, "deleteBankItem", result);
|
|
}
|
|
else if (message == "renameBank")
|
|
{
|
|
RenameBankBody body;
|
|
pReader->read(&body);
|
|
|
|
std::stringstream tOut;
|
|
json_writer tWriter(tOut);
|
|
tWriter.write(body.newName_);
|
|
std::string tJson = tOut.str();
|
|
std::stringstream tIn(tJson);
|
|
json_reader tReader(tIn);
|
|
std::string tResult;
|
|
tReader.read(&tResult);
|
|
|
|
body.newName_ = tResult;
|
|
|
|
|
|
try {
|
|
model.RenameBank(this->clientId,body.bankId_, body.newName_);
|
|
this->Reply(replyTo, "renameBank");
|
|
} catch (const std::exception &e)
|
|
{
|
|
this->SendError(replyTo,std::string(e.what()));
|
|
}
|
|
}
|
|
else if (message == "openBank")
|
|
{
|
|
int64_t bankId = -1;
|
|
pReader->read(&bankId);
|
|
try {
|
|
model.OpenBank(this->clientId,bankId);;
|
|
this->Reply(replyTo, "openBank");
|
|
} catch (const std::exception &e)
|
|
{
|
|
this->SendError(replyTo,std::string(e.what()));
|
|
}
|
|
}
|
|
else if (message == "saveBankAs")
|
|
{
|
|
RenameBankBody body;
|
|
pReader->read(&body);
|
|
try {
|
|
int64_t newId = model.SaveBankAs(this->clientId,body.bankId_, body.newName_);
|
|
this->Reply(replyTo, "saveBankAs",newId);
|
|
} catch (const std::exception &e)
|
|
{
|
|
this->SendError(replyTo,std::string(e.what()));
|
|
}
|
|
}
|
|
else if (message == "renamePresetItem")
|
|
{
|
|
RenamePresetBody body;
|
|
pReader->read(&body);
|
|
|
|
bool result = model.RenamePreset(body.clientId_, body.instanceId_, body.name_);
|
|
this->Reply(replyTo, "renamePresetItem", result);
|
|
}
|
|
else if (message == "copyPreset")
|
|
{
|
|
CopyPresetBody body;
|
|
pReader->read(&body);
|
|
int64_t result = model.CopyPreset(body.clientId_, body.fromId_, body.toId_);
|
|
this->Reply(replyTo, "copyPreset", result);
|
|
}
|
|
else if (message == "copyPluginPreset")
|
|
{
|
|
CopyPluginPresetBody body;
|
|
pReader->read(&body);
|
|
uint64_t result = model.CopyPluginPreset(body.pluginUri_,body.instanceId_);
|
|
this->Reply(replyTo, "copyPluginPreset", result);
|
|
}
|
|
else if (message =="getLv2Parameter")
|
|
{
|
|
GetLv2ParameterBody body;
|
|
pReader->read(&body);
|
|
|
|
model.GetLv2Parameter(
|
|
this->clientId,
|
|
body.instanceId_,
|
|
body.uri_,
|
|
[this,replyTo] (const std::string &jsonResult)
|
|
{
|
|
this->JsonReply(replyTo,"getLv2Parameter",jsonResult.c_str());
|
|
},
|
|
[this,replyTo] (const std::string &error) {
|
|
this->SendError(replyTo,error.c_str());
|
|
});
|
|
|
|
}
|
|
else if (message == "monitorPort")
|
|
{
|
|
|
|
MonitorPortBody body;
|
|
pReader->read(&body);
|
|
|
|
int64_t subscriptionHandle = model.MonitorPort(
|
|
body.instanceId_,
|
|
body.key_,
|
|
body.updateRate_,
|
|
[this](int64_t subscriptionHandle_, float value)
|
|
{
|
|
MonitorResultBody body;
|
|
body.subscriptionHandle_ = subscriptionHandle_;
|
|
body.value_ = value;
|
|
if (!waitingForPortMonitorAck(subscriptionHandle_)) //only one outstanding response.
|
|
{
|
|
this->Request<bool, MonitorResultBody>( // send the message and wait for a response.
|
|
"onMonitorPortOutput",
|
|
body,
|
|
[this, subscriptionHandle_](const bool &result)
|
|
{
|
|
this->portMonitorAck(subscriptionHandle_); // please can we have some more.
|
|
// (....complexity not worth the effort. :-( )
|
|
},
|
|
[](const std::exception &e)
|
|
{
|
|
Lv2Log::debug("Failed to monitor port output. (%s)", e.what());
|
|
});
|
|
}
|
|
});
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
activePortMonitors.push_back(
|
|
PortMonitorSubscription{subscriptionHandle, body.instanceId_,false, body.key_});
|
|
}
|
|
|
|
this->Reply(replyTo, "monitorPort", subscriptionHandle);
|
|
}
|
|
else if (message == "unmonitorPort")
|
|
{
|
|
int64_t subscriptionHandle;
|
|
pReader->read(&subscriptionHandle);
|
|
{
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i)
|
|
{
|
|
if ((*i).subscriptionHandle == subscriptionHandle)
|
|
{
|
|
this->activePortMonitors.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
model.UnmonitorPort(subscriptionHandle);
|
|
}
|
|
}
|
|
else if (message == "addVuSubscription")
|
|
{
|
|
int64_t instanceId = -1;
|
|
|
|
pReader->read(&instanceId);
|
|
|
|
int64_t subscriptionHandle = model.AddVuSubscription(instanceId);
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
activeVuSubscriptions.push_back(VuSubscription{subscriptionHandle, instanceId});
|
|
}
|
|
this->Reply(replyTo, "addVuSubscription", subscriptionHandle);
|
|
}
|
|
else if (message == "removeVuSubscription")
|
|
{
|
|
int64_t subscriptionHandle = -1;
|
|
pReader->read(&subscriptionHandle);
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
|
|
for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i)
|
|
{
|
|
if (i->subscriptionHandle == subscriptionHandle)
|
|
{
|
|
activeVuSubscriptions.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
model.RemoveVuSubscription(subscriptionHandle);
|
|
}
|
|
else if (message == "imageList")
|
|
{
|
|
|
|
this->Reply(replyTo, "imageList", imageList);
|
|
}
|
|
else
|
|
{
|
|
Lv2Log::error("Unknown message received: %s", message.c_str());
|
|
SendError(replyTo, std::string("Unknown message: ") + message);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
virtual void
|
|
onReceive(const std::string_view &text)
|
|
{
|
|
view_istream<char> s(text);
|
|
|
|
json_reader reader(s);
|
|
// read top level object until we have message
|
|
int64_t replyTo = -1;
|
|
int64_t reply = -1;
|
|
std::string message;
|
|
|
|
try
|
|
{
|
|
reader.consume('[');
|
|
reader.consume('{');
|
|
|
|
while (true)
|
|
{
|
|
if (reader.peek() == '}')
|
|
{
|
|
reader.consume('}');
|
|
break;
|
|
}
|
|
std::string name = reader.read_string();
|
|
reader.consume(':');
|
|
if (name == "reply")
|
|
{
|
|
reader.read(&reply);
|
|
}
|
|
if (name == "replyTo")
|
|
{
|
|
reader.read(&replyTo);
|
|
}
|
|
if (name == "message")
|
|
{
|
|
message = reader.read_string();
|
|
}
|
|
if (reader.peek() == ',')
|
|
{
|
|
reader.consume(',');
|
|
continue;
|
|
}
|
|
}
|
|
if (reader.peek() == ',')
|
|
{
|
|
reader.consume(',');
|
|
handleMessage(reply, replyTo, message, &reader);
|
|
}
|
|
else
|
|
{
|
|
handleMessage(reply, replyTo, message, nullptr);
|
|
}
|
|
// Handle message.
|
|
}
|
|
catch (const PiPedalException &e)
|
|
{
|
|
SendError(replyTo, e.what());
|
|
}
|
|
catch (const std::exception & e)
|
|
{
|
|
SendError(replyTo, e.what());
|
|
}
|
|
}
|
|
|
|
public:
|
|
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection)
|
|
{
|
|
ChannelSelectionChangedBody body;
|
|
body.clientId_ = clientId;
|
|
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
|
|
Send("onChannelSelectionChanged", body);
|
|
}
|
|
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets)
|
|
{
|
|
PresetsChangedBody body;
|
|
body.clientId_ = clientId;
|
|
body.presets_ = const_cast<PresetIndex *>(&presets);
|
|
Send("onPresetsChanged", body);
|
|
}
|
|
virtual void OnPluginPresetsChanged(const std::string&pluginUri)
|
|
{
|
|
Send("onPluginPresetsChanged", pluginUri);
|
|
}
|
|
virtual void OnJackConfigurationChanged(const JackConfiguration &jackConfiguration)
|
|
{
|
|
Send("onJackConfigurationChanged", jackConfiguration);
|
|
}
|
|
|
|
virtual void OnLoadPluginPreset(int64_t instanceId,const std::vector<ControlValue>&controlValues) {
|
|
OnLoadPluginPresetBody body;
|
|
body.instanceId_ = instanceId;
|
|
body.controlValues_ = const_cast<std::vector<ControlValue>&>(controlValues);
|
|
Send("onLoadPluginPreset",body);
|
|
}
|
|
|
|
|
|
int updateRequestOutstanding = 0;
|
|
bool vuUpdateDropped = false;
|
|
|
|
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
if (updateRequestOutstanding < 5) // throttle to accomodate a web page that can't keep up.
|
|
{
|
|
vuUpdateDropped = false;
|
|
for (int i = 0; i < updates.size(); ++i)
|
|
{
|
|
const VuUpdate &vuUpdate = updates[i];
|
|
bool interested = false;
|
|
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
|
|
{
|
|
if (activeVuSubscriptions[i].instanceId == vuUpdate.instanceId_)
|
|
{
|
|
interested = true;
|
|
break;
|
|
}
|
|
}
|
|
if (interested)
|
|
{
|
|
updateRequestOutstanding++;
|
|
this->Request<bool, VuUpdate>(
|
|
"onVuUpdate",
|
|
vuUpdate,
|
|
[this](const bool &result)
|
|
{
|
|
this->updateRequestOutstanding--;
|
|
},
|
|
[this](const std::exception &)
|
|
{
|
|
this->updateRequestOutstanding--;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!vuUpdateDropped)
|
|
{
|
|
vuUpdateDropped = true;
|
|
Lv2Log::debug("Vu Update(s) dropped.");
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void OnControlChanged(int64_t clientId, int64_t instanceId, const std::string &key, float value)
|
|
{
|
|
ControlChangedBody body;
|
|
body.clientId_ = clientId;
|
|
body.instanceId_ = instanceId;
|
|
body.symbol_ = key;
|
|
body.value_ = value;
|
|
Send("onControlChanged", body);
|
|
}
|
|
|
|
class DeferredValue {
|
|
public:
|
|
int64_t instanceId;
|
|
std::string symbol;
|
|
float value;
|
|
};
|
|
|
|
std::vector<DeferredValue> deferredValues;
|
|
bool midiValueChangedOutstanding = false;
|
|
|
|
|
|
virtual void OnMidiValueChanged(int64_t instanceId, const std::string&symbol, float value) {
|
|
if (midiValueChangedOutstanding)
|
|
{
|
|
for (size_t i = 0; i < deferredValues.size(); ++i) {
|
|
auto &deferredValue = deferredValues[i];
|
|
if (deferredValue.instanceId == instanceId && deferredValue.symbol == symbol)
|
|
{
|
|
deferredValue.value = value;
|
|
return;
|
|
}
|
|
}
|
|
DeferredValue newValue {instanceId,symbol,value};
|
|
deferredValues.push_back(newValue);
|
|
} else {
|
|
midiValueChangedOutstanding = true;
|
|
ControlChangedBody body;
|
|
body.clientId_ = -1;
|
|
body.instanceId_ = instanceId;
|
|
body.symbol_ = symbol;
|
|
body.value_ = value;
|
|
Request<bool>("onMidiValueChanged", body,
|
|
[this] (const bool& value)
|
|
{
|
|
this->midiValueChangedOutstanding = false;
|
|
if (this->deferredValues.size() != 0)
|
|
{
|
|
DeferredValue value = deferredValues[0];
|
|
deferredValues.erase(deferredValues.begin());
|
|
this->OnMidiValueChanged(value.instanceId,value.symbol,value.value);
|
|
}
|
|
|
|
},
|
|
[] (const std::exception &e) {
|
|
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
int outstandingNotifyAtomOutputs = 0;
|
|
|
|
class PendingNotifyAtomOutput {
|
|
public:
|
|
int64_t clientHandle;
|
|
uint64_t instanceId;
|
|
std::string atomType;
|
|
std::string json;
|
|
};
|
|
|
|
std::vector<PendingNotifyAtomOutput> pendingNotifyAtomOutputs;
|
|
|
|
|
|
void OnAckNotifyAtomOutput() {
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
if (--outstandingNotifyAtomOutputs <= 0)
|
|
{
|
|
outstandingNotifyAtomOutputs = 0;
|
|
if (pendingNotifyAtomOutputs.size() != 0)
|
|
{
|
|
PendingNotifyAtomOutput t = pendingNotifyAtomOutputs[0];
|
|
pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin());
|
|
OnNotifyAtomOutput(
|
|
t.clientHandle,
|
|
t.instanceId,
|
|
t.atomType,
|
|
t.json);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void OnNotifyAtomOutput(int64_t clientHandle,uint64_t instanceId,const std::string&atomType, const std::string&atomJson)
|
|
{
|
|
NotifyAtomOutputBody body;
|
|
body.clientHandle_ = clientHandle;
|
|
body.instanceId_ = instanceId;
|
|
body.atomJson_ = atomJson;
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
|
|
|
if (outstandingNotifyAtomOutputs != 0)
|
|
{
|
|
for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i)
|
|
{
|
|
auto &output = pendingNotifyAtomOutputs[i];
|
|
if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.atomType == atomType)
|
|
{
|
|
output.json = atomJson;
|
|
return;
|
|
}
|
|
}
|
|
pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{ clientHandle, instanceId,atomType,atomJson });
|
|
return;
|
|
}
|
|
++outstandingNotifyAtomOutputs;
|
|
}
|
|
|
|
Request<bool>("onNotifyAtomOut",body,
|
|
[this] (const bool& value)
|
|
{
|
|
this->OnAckNotifyAtomOutput();
|
|
},
|
|
[] (const std::exception &e) {
|
|
|
|
}
|
|
);
|
|
|
|
}
|
|
virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl)
|
|
{
|
|
NotifyMidiListenerBody body;
|
|
body.clientHandle_ = clientHandle;
|
|
body.isNote_ = isNote;
|
|
body.noteOrControl_ = noteOrControl;
|
|
|
|
Send("onNotifyMidiListener",body);
|
|
}
|
|
|
|
|
|
|
|
virtual void OnBankIndexChanged(const BankIndex& bankIndex)
|
|
{
|
|
Send("onBanksChanged",bankIndex);
|
|
}
|
|
|
|
virtual void OnJackServerSettingsChanged(const JackServerSettings& jackServerSettings)
|
|
{
|
|
Send("onJackServerSettingsChanged",jackServerSettings);
|
|
}
|
|
|
|
virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) {
|
|
Send("onWifiConfigSettingsChanged",wifiConfigSettings);
|
|
}
|
|
|
|
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard)
|
|
{
|
|
SetCurrentPedalBoardBody body;
|
|
body.clientId_ = clientId;
|
|
body.pedalBoard_ = pedalBoard;
|
|
Send("onPedalBoardChanged", body);
|
|
}
|
|
|
|
|
|
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled)
|
|
{
|
|
PedalBoardItemEnabledBody body;
|
|
body.clientId_ = clientId;
|
|
body.instanceId_ = pedalItemId;
|
|
body.enabled_ = enabled;
|
|
Send("onItemEnabledChanged", body);
|
|
}
|
|
};
|
|
|
|
std::atomic<uint64_t> PiPedalSocketHandler::nextClientId = 0;
|
|
|
|
class PiPedalSocketFactory : public ISocketFactory
|
|
{
|
|
private:
|
|
PiPedalModel &model;
|
|
|
|
public:
|
|
virtual ~PiPedalSocketFactory()
|
|
{
|
|
}
|
|
PiPedalSocketFactory(PiPedalModel &model)
|
|
: model(model)
|
|
{
|
|
}
|
|
|
|
public:
|
|
virtual bool wants(const uri &request)
|
|
{
|
|
if (request.segment(0) == "pipedal")
|
|
return true;
|
|
return false;
|
|
}
|
|
virtual std::shared_ptr<SocketHandler> CreateHandler(const uri &request)
|
|
{
|
|
return std::make_shared<PiPedalSocketHandler>(model);
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<ISocketFactory> pipedal::MakePiPedalSocketFactory(PiPedalModel &model)
|
|
{
|
|
return std::make_shared<PiPedalSocketFactory>(model);
|
|
}
|
|
|