Notify web UI of assignments, and listeners of changes from UI
This commit is contained in:
parent
105e00a1ef
commit
97efa774aa
@ -152,6 +152,16 @@ struct Plugin : public clap_plugin {
|
|||||||
this->get_extension = plugin_get_extension;
|
this->get_extension = plugin_get_extension;
|
||||||
this->on_main_thread = plugin_on_main_thread;
|
this->on_main_thread = plugin_on_main_thread;
|
||||||
|
|
||||||
|
effect.paramListenerRange = [&](stfx::web::ParamContext context, double v){
|
||||||
|
std::cout << "param #" << context.index << " change: " << v << "\n";
|
||||||
|
};
|
||||||
|
effect.paramListenerGesture = [&](stfx::web::ParamContext context, bool gesture){
|
||||||
|
std::cout << "param #" << context.index << " gesture: " << gesture << "\n";
|
||||||
|
};
|
||||||
|
effect.paramListenerStepped = [&](stfx::web::ParamContext context, int v){
|
||||||
|
std::cout << "param #" << context.index << " change: " << v << "\n";
|
||||||
|
};
|
||||||
|
|
||||||
scanParams();
|
scanParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,6 +319,8 @@ struct Plugin : public clap_plugin {
|
|||||||
inputBuffers.resize(0);
|
inputBuffers.resize(0);
|
||||||
outputBuffers.resize(0);
|
outputBuffers.resize(0);
|
||||||
|
|
||||||
|
plugin.host->request_callback(plugin.host);
|
||||||
|
|
||||||
if (plugin.effect.hasPendingWebMessage()) {
|
if (plugin.effect.hasPendingWebMessage()) {
|
||||||
plugin.host->request_callback(plugin.host);
|
plugin.host->request_callback(plugin.host);
|
||||||
}
|
}
|
||||||
@ -368,6 +380,7 @@ struct Plugin : public clap_plugin {
|
|||||||
}
|
}
|
||||||
template<class PR>
|
template<class PR>
|
||||||
RangeParamInfo & range(const char *key, PR ¶m) {
|
RangeParamInfo & range(const char *key, PR ¶m) {
|
||||||
|
param.context.index = params.size();
|
||||||
params.emplace_back();
|
params.emplace_back();
|
||||||
auto &entry = params.back();
|
auto &entry = params.back();
|
||||||
entry.id = crc.copy().addString(key, true).done();
|
entry.id = crc.copy().addString(key, true).done();
|
||||||
@ -376,6 +389,7 @@ struct Plugin : public clap_plugin {
|
|||||||
}
|
}
|
||||||
template<class PS>
|
template<class PS>
|
||||||
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
||||||
|
param.context.index = params.size();
|
||||||
params.emplace_back();
|
params.emplace_back();
|
||||||
auto &entry = params.back();
|
auto &entry = params.back();
|
||||||
entry.id = crc.copy().addString(key, true).done();
|
entry.id = crc.copy().addString(key, true).done();
|
||||||
@ -617,7 +631,6 @@ struct Plugin : public clap_plugin {
|
|||||||
StateReader storage{buffer};
|
StateReader storage{buffer};
|
||||||
plugin.effect.state(storage);
|
plugin.effect.state(storage);
|
||||||
|
|
||||||
plugin.effect.markStateDirty();
|
|
||||||
plugin.sendWebMessages(); // should already be on the main thread
|
plugin.sendWebMessages(); // should already be on the main thread
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,11 @@
|
|||||||
grid-area: header;
|
grid-area: header;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin: 0.5rem;
|
||||||
|
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0.1ex;
|
||||||
|
transform: scale(0.95, 1);
|
||||||
}
|
}
|
||||||
#params {
|
#params {
|
||||||
grid-area: params;
|
grid-area: params;
|
||||||
@ -77,6 +82,7 @@
|
|||||||
top: 30%;
|
top: 30%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
letter-spacing: -0.1ex;
|
||||||
}
|
}
|
||||||
.param-range-units {
|
.param-range-units {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -99,14 +105,24 @@
|
|||||||
<label class="param-range" @if="${d => d.$type == 'ParamRange'}">
|
<label class="param-range" @if="${d => d.$type == 'ParamRange'}">
|
||||||
<div class="param-range-name">{name}</div>
|
<div class="param-range-name">{name}</div>
|
||||||
<script>
|
<script>
|
||||||
function move(data, dx, dy) {
|
function move(data, dx, dy, element) {
|
||||||
data.rangeUnit = Math.min(1, Math.max(0, data.rangeUnit - dy/250));
|
let prev = data.gesture ? data._gestureUnit : data.rangeUnit;
|
||||||
|
data._gestureUnit = data.rangeUnit = Math.min(1, Math.max(0, prev - dy/250));
|
||||||
}
|
}
|
||||||
function press(data, count) {
|
function press(data, count) {
|
||||||
if (count == 2) data.value = data.defaultValue;
|
data.gesture = true;
|
||||||
|
data._gestureUnit = data.rangeUnit;
|
||||||
|
if (count == 2) {
|
||||||
|
console.log("data.gesture", data.gesture);
|
||||||
|
data.value = data.defaultValue;
|
||||||
|
data.gesture = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function unpress(data) {
|
||||||
|
data.gesture = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class="param-range-value" $move="${move}" $press="${press}">
|
<div class="param-range-value" $move="${move}" $press="${press}" $unpress="${unpress}">
|
||||||
<canvas class="param-range-dial" $update="${drawDial}"></canvas>
|
<canvas class="param-range-dial" $update="${drawDial}"></canvas>
|
||||||
<output class="param-range-text">{text}</output><br>
|
<output class="param-range-text">{text}</output><br>
|
||||||
<div class="param-range-units">{textUnits}</div>
|
<div class="param-range-units">{textUnits}</div>
|
||||||
@ -129,15 +145,23 @@
|
|||||||
context.translate(1, 1);
|
context.translate(1, 1);
|
||||||
|
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.ellipse(0, 0, 1, 1, 0, 0, Math.PI*2);
|
context.ellipse(0, 0, 0.8, 0.8, 0, 0, Math.PI*2);
|
||||||
context.fillStyle = '#FFF';
|
context.fillStyle = '#FFF';
|
||||||
context.fill();
|
context.fill();
|
||||||
|
|
||||||
context.beginPath();
|
|
||||||
context.ellipse(0, 0, 0.9, 0.9, 0, Math.PI*-1.25, Math.PI*(-1.249 + data.rangeUnit*1.499));
|
|
||||||
context.strokeStyle = '#000';
|
|
||||||
context.lineWidth = 0.2;
|
context.lineWidth = 0.2;
|
||||||
context.lineCap = 'round';
|
context.lineCap = 'round';
|
||||||
|
context.beginPath();
|
||||||
|
context.ellipse(0, 0, 0.9, 0.9, 0, Math.PI*-1.25, Math.PI*0.25);
|
||||||
|
context.strokeStyle = '#0001';
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
let rangeUnit = data.rangeUnit;
|
||||||
|
if (data.gesture) rangeUnit = data._gestureUnit;
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.ellipse(0, 0, 0.9, 0.9, 0, Math.PI*-1.25, Math.PI*(-1.249 + rangeUnit*1.499));
|
||||||
|
context.strokeStyle = '#000';
|
||||||
context.stroke();
|
context.stroke();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
400
stfx/ui/web-ui.h
400
stfx/ui/web-ui.h
@ -12,148 +12,13 @@
|
|||||||
|
|
||||||
namespace stfx { namespace web {
|
namespace stfx { namespace web {
|
||||||
|
|
||||||
/* Given an STFX template which (when insantiated) has inheritance like:
|
struct ParamContext {
|
||||||
|
union {
|
||||||
EffectSTFX : Effect
|
void *pointer;
|
||||||
|
size_t index;
|
||||||
This produces a template with inheritance like:
|
|
||||||
|
|
||||||
WebSTFX : EffectSTFX : WebBase : Effect
|
|
||||||
|
|
||||||
The WebBase template replaces the ParamRange/ParamStepped classes, so that the UI can be updated when they change. The outer WebSTFX class adds methods for forwarding messages between the effect and its webview.
|
|
||||||
*/
|
|
||||||
|
|
||||||
template<template<class, class...> class EffectSTFX>
|
|
||||||
struct WebUIHelper {
|
|
||||||
|
|
||||||
struct MaybeChanged {
|
|
||||||
virtual void maybeChanged() = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebParamScanner {
|
ParamContext() : index(0) {}
|
||||||
MaybeChanged *effect;
|
|
||||||
|
|
||||||
// Do nothing for most types
|
|
||||||
template<class V>
|
|
||||||
void operator()(const char *key, V &v) {
|
|
||||||
if constexpr (std::is_void_v<decltype(v.state(*this))>) {
|
|
||||||
v.state(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra methods we add for STFX storage
|
|
||||||
void info(const char *, const char *) {}
|
|
||||||
int version(int v) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
template<class PR>
|
|
||||||
RangeParamInfo & range(const char *key, PR ¶m) {
|
|
||||||
param.effect = effect;
|
|
||||||
param.info = std::unique_ptr<RangeParamInfo>{
|
|
||||||
new RangeParamInfo(param)
|
|
||||||
};
|
|
||||||
return *param.info;
|
|
||||||
}
|
|
||||||
template<class PS>
|
|
||||||
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
|
||||||
param.effect = effect;
|
|
||||||
param.info = std::unique_ptr<SteppedParamInfo>{
|
|
||||||
new SteppedParamInfo(param)
|
|
||||||
};
|
|
||||||
return *param.info;
|
|
||||||
}
|
|
||||||
template<class V>
|
|
||||||
bool changed(const char *key, V &v) {
|
|
||||||
(*this)(key, v);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void invalidate(const char *) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class Effect>
|
|
||||||
class WebBase : public Effect {
|
|
||||||
using SuperRange = typename Effect::ParamRange;
|
|
||||||
using SuperStepped = typename Effect::ParamStepped;
|
|
||||||
public:
|
|
||||||
|
|
||||||
using Effect::Effect;
|
|
||||||
|
|
||||||
struct ParamRange : public SuperRange {
|
|
||||||
using SuperRange::SuperRange;
|
|
||||||
|
|
||||||
ParamRange & operator=(double v) {
|
|
||||||
SuperRange::operator=(v);
|
|
||||||
effect->maybeChanged();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Storage>
|
|
||||||
void state(Storage &storage) {
|
|
||||||
double prevV = *this;
|
|
||||||
SuperRange::state(storage);
|
|
||||||
|
|
||||||
if (storage.extra()) {
|
|
||||||
storage.extra("$type", "ParamRange");
|
|
||||||
storage.extra("name", info->name);
|
|
||||||
storage.extra("defaultValue", info->defaultValue);
|
|
||||||
std::string text, units;
|
|
||||||
info->toString((double)*this, text, units);
|
|
||||||
storage.extra("text", text);
|
|
||||||
storage.extra("textUnits", units);
|
|
||||||
|
|
||||||
double unit = info->toUnit(*this);
|
|
||||||
if (storage.changed("rangeUnit", unit)) {
|
|
||||||
*this = info->fromUnit(unit);
|
|
||||||
storage.invalidate("value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevV != *this) {
|
|
||||||
effect->maybeChanged();
|
|
||||||
storage.invalidate("rangeUnit");
|
|
||||||
storage.invalidate("text");
|
|
||||||
storage.invalidate("textUnits");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend struct WebParamScanner;
|
|
||||||
MaybeChanged *effect = nullptr;
|
|
||||||
std::unique_ptr<RangeParamInfo> info;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ParamStepped : public SuperStepped {
|
|
||||||
using SuperStepped::SuperStepped;
|
|
||||||
|
|
||||||
ParamStepped & operator=(double v) {
|
|
||||||
SuperStepped::operator=(v);
|
|
||||||
effect->maybeChanged();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Storage>
|
|
||||||
void state(Storage &storage) {
|
|
||||||
int prevV = *this;
|
|
||||||
SuperStepped::state(storage);
|
|
||||||
|
|
||||||
if (storage.extra()) {
|
|
||||||
storage.extra("name", info->name);
|
|
||||||
storage.extra("$type", "ParamStepped");
|
|
||||||
std::string text = info->toString((int)*this);
|
|
||||||
storage.extra("text", text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevV != *this) {
|
|
||||||
effect->maybeChanged();
|
|
||||||
storage.invalidate("text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend struct WebParamScanner;
|
|
||||||
MaybeChanged *effect = nullptr;
|
|
||||||
std::unique_ptr<SteppedParamInfo> info;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebStateWriter : public storage::StorageCborWriter<false, WebStateWriter> {
|
struct WebStateWriter : public storage::StorageCborWriter<false, WebStateWriter> {
|
||||||
@ -235,20 +100,241 @@ struct WebUIHelper {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Effect, class... ExtraArgs>
|
template<class S>
|
||||||
struct WebSTFX : public EffectSTFX<WebBase<Effect>, ExtraArgs...>, MaybeChanged {
|
constexpr bool storageIsWeb() {return false;}
|
||||||
// Inherit constructor(s)
|
template<>
|
||||||
using Super = EffectSTFX<WebBase<Effect>, ExtraArgs...>;
|
constexpr bool storageIsWeb<WebStateWriter>() {return true;}
|
||||||
|
template<>
|
||||||
|
constexpr bool storageIsWeb<WebStateReader>() {return true;}
|
||||||
|
|
||||||
template<class... Args>
|
/* Given an STFX template which (when insantiated) has inheritance like:
|
||||||
WebSTFX(Args... args) : Super(args...) {
|
|
||||||
WebParamScanner scanner{this};
|
EffectSTFX : Effect
|
||||||
this->state(scanner);
|
|
||||||
|
This produces a template with inheritance like:
|
||||||
|
|
||||||
|
WebSTFX : EffectSTFX : WebBase : Effect
|
||||||
|
|
||||||
|
The WebBase template replaces the ParamRange/ParamStepped classes, so that the UI can be updated when they change. The outer WebSTFX class adds methods for forwarding messages between the effect and its webview.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template<template<class, class...> class EffectSTFX>
|
||||||
|
struct WebUIHelper {
|
||||||
|
|
||||||
|
template<class Effect, class SubClass>
|
||||||
|
class WebBase : public Effect {
|
||||||
|
using SuperRange = typename Effect::ParamRange;
|
||||||
|
using SuperStepped = typename Effect::ParamStepped;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct WebParamScanner {
|
||||||
|
SubClass *effect;
|
||||||
|
std::vector<std::string> scope;
|
||||||
|
|
||||||
|
WebParamScanner(SubClass *effect) : effect(effect) {}
|
||||||
|
|
||||||
|
// Do nothing for most types
|
||||||
|
template<class V>
|
||||||
|
void operator()(const char *key, V &v) {
|
||||||
|
scope.emplace_back(key);
|
||||||
|
// TODO: detect objects with .state() somehow
|
||||||
|
v.state(*this);
|
||||||
|
scope.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra methods we add for STFX storage
|
||||||
|
void info(const char *, const char *) {}
|
||||||
|
int version(int v) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
template<class PR>
|
||||||
|
RangeParamInfo & range(const char *key, PR ¶m) {
|
||||||
|
param.effect = effect;
|
||||||
|
param.scope = scope;
|
||||||
|
param.scope.emplace_back(key);
|
||||||
|
param.info = std::unique_ptr<RangeParamInfo>{
|
||||||
|
new RangeParamInfo(param)
|
||||||
};
|
};
|
||||||
|
return *param.info;
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
||||||
|
param.effect = effect;
|
||||||
|
param.scope = scope;
|
||||||
|
param.scope.emplace_back(key);
|
||||||
|
param.info = std::unique_ptr<SteppedParamInfo>{
|
||||||
|
new SteppedParamInfo(param)
|
||||||
|
};
|
||||||
|
return *param.info;
|
||||||
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Effect::Effect;
|
||||||
|
|
||||||
std::string webPage = "html/generic.html";
|
std::string webPage = "html/generic.html";
|
||||||
int webWidth = 640, webHeight = 480;
|
int webWidth = 640, webHeight = 480;
|
||||||
|
|
||||||
|
struct ParamRange : public SuperRange {
|
||||||
|
using SuperRange::SuperRange;
|
||||||
|
|
||||||
|
ParamContext context;
|
||||||
|
|
||||||
|
ParamRange & operator=(double v) {
|
||||||
|
SuperRange::operator=(v);
|
||||||
|
sendUpdateMessage();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
double prevV = *this;
|
||||||
|
bool prevGesture = gesture;
|
||||||
|
SuperRange::state(storage);
|
||||||
|
|
||||||
|
constexpr bool isWeb = storageIsWeb<Storage>();
|
||||||
|
if (isWeb) { // a bunch of extra state
|
||||||
|
storage.extra("$type", "ParamRange");
|
||||||
|
storage.extra("name", info->name);
|
||||||
|
storage.extra("defaultValue", info->defaultValue);
|
||||||
|
std::string text, units;
|
||||||
|
info->toString((double)*this, text, units);
|
||||||
|
storage.extra("text", text);
|
||||||
|
storage.extra("textUnits", units);
|
||||||
|
storage("gesture", gesture);
|
||||||
|
|
||||||
|
double unit = info->toUnit(*this);
|
||||||
|
if (storage.changed("rangeUnit", unit)) {
|
||||||
|
SuperRange::operator=(info->fromUnit(unit));
|
||||||
|
storage.invalidate("value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevV != *this) {
|
||||||
|
storage.invalidate("rangeUnit");
|
||||||
|
storage.invalidate("text");
|
||||||
|
storage.invalidate("textUnits");
|
||||||
|
if (isWeb) {
|
||||||
|
if (effect->paramListenerRange) {
|
||||||
|
effect->paramListenerRange(context, *this);
|
||||||
|
}
|
||||||
|
effect->requestEntireState(); // TODO: shouldn't be necessary once we have `.invalidate()` working
|
||||||
|
} else {
|
||||||
|
sendUpdateMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gesture != prevGesture) {
|
||||||
|
if (isWeb && effect->paramListenerGesture) {
|
||||||
|
effect->paramListenerGesture(context, gesture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct WebParamScanner;
|
||||||
|
SubClass *effect = nullptr;
|
||||||
|
std::vector<std::string> scope;
|
||||||
|
std::unique_ptr<RangeParamInfo> info;
|
||||||
|
bool gesture = false;
|
||||||
|
|
||||||
|
void sendUpdateMessage() {
|
||||||
|
if (auto *m = effect->getEmptyMessage()) {
|
||||||
|
signalsmith::cbor::CborWriter cbor{m->bytes};
|
||||||
|
for (auto &key : scope) {
|
||||||
|
cbor.openMap(1);
|
||||||
|
cbor.addUtf8(key);
|
||||||
|
}
|
||||||
|
WebStateWriter storage{cbor};
|
||||||
|
state(storage);
|
||||||
|
m->markReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParamStepped : public SuperStepped {
|
||||||
|
using SuperStepped::SuperStepped;
|
||||||
|
|
||||||
|
ParamContext context;
|
||||||
|
|
||||||
|
ParamStepped & operator=(int v) {
|
||||||
|
SuperStepped::operator=(v);
|
||||||
|
sendUpdateMessage();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
int prevV = *this;
|
||||||
|
SuperStepped::state(storage);
|
||||||
|
|
||||||
|
constexpr bool isWeb = storageIsWeb<Storage>();
|
||||||
|
if (isWeb) {
|
||||||
|
storage.extra("$type", "ParamStepped");
|
||||||
|
storage.extra("low", info->low);
|
||||||
|
storage.extra("high", info->high);
|
||||||
|
storage.extra("name", info->name);
|
||||||
|
std::string text = info->toString((int)*this);
|
||||||
|
storage.extra("text", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevV != *this) {
|
||||||
|
storage.invalidate("text");
|
||||||
|
if (isWeb) {
|
||||||
|
if (effect->paramListenerStepped) {
|
||||||
|
effect->paramListenerStepped(context, *this);
|
||||||
|
}
|
||||||
|
effect->requestEntireState(); // TODO: shouldn't be necessary once we have `.invalidate()` working
|
||||||
|
} else {
|
||||||
|
sendUpdateMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct WebParamScanner;
|
||||||
|
SubClass *effect = nullptr;
|
||||||
|
std::vector<std::string> scope;
|
||||||
|
std::unique_ptr<SteppedParamInfo> info;
|
||||||
|
|
||||||
|
void sendUpdateMessage() {
|
||||||
|
if (auto *m = effect->getEmptyMessage()) {
|
||||||
|
signalsmith::cbor::CborWriter cbor{m->bytes};
|
||||||
|
for (auto &key : scope) {
|
||||||
|
cbor.openMap(1);
|
||||||
|
cbor.addUtf8(key);
|
||||||
|
}
|
||||||
|
WebStateWriter storage{cbor};
|
||||||
|
state(storage);
|
||||||
|
m->markReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Effect, class... ExtraArgs>
|
||||||
|
struct WebSTFX : public EffectSTFX<WebBase<Effect, WebSTFX<Effect, ExtraArgs...>>, ExtraArgs...> {
|
||||||
|
/* TODO: without the ExtraArgs, it would be a bit neater:
|
||||||
|
EffectSTFX<WebBase<Effect, WebSTFX<Effect>>>
|
||||||
|
*/
|
||||||
|
using Super = EffectSTFX<WebBase<Effect, WebSTFX<Effect, ExtraArgs...>>, ExtraArgs...>;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
WebSTFX(Args... args) : Super(args...) {
|
||||||
|
typename Super::WebParamScanner scanner{this};
|
||||||
|
this->state(scanner);
|
||||||
|
};
|
||||||
|
|
||||||
|
// These are called when the parameter is changed from the web UI
|
||||||
|
std::function<void(ParamContext, double)> paramListenerRange;
|
||||||
|
std::function<void(ParamContext, bool)> paramListenerGesture;
|
||||||
|
std::function<void(ParamContext, int)> paramListenerStepped;
|
||||||
|
|
||||||
struct WebMessage {
|
struct WebMessage {
|
||||||
std::vector<unsigned char> bytes;
|
std::vector<unsigned char> bytes;
|
||||||
|
|
||||||
@ -259,8 +345,11 @@ struct WebUIHelper {
|
|||||||
void sent() {
|
void sent() {
|
||||||
readyToSend.clear();
|
readyToSend.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct WebSTFX;
|
friend class WebSTFX;
|
||||||
|
friend struct Super::ParamRange;
|
||||||
|
friend struct Super::ParamStepped;
|
||||||
std::atomic_flag readyToSend = ATOMIC_FLAG_INIT;
|
std::atomic_flag readyToSend = ATOMIC_FLAG_INIT;
|
||||||
|
|
||||||
void markReady() {
|
void markReady() {
|
||||||
@ -314,17 +403,10 @@ struct WebUIHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void maybeChanged() override {
|
|
||||||
std::cout << "web effect maybe changed\n";
|
|
||||||
// TODO: something better
|
|
||||||
requestEntireState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void markStateDirty() {
|
|
||||||
requestEntireState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend struct Super::ParamRange;
|
||||||
|
friend struct Super::ParamStepped;
|
||||||
|
|
||||||
std::vector<WebMessage> queue = std::vector<WebMessage>(64); // power of 2 so that overflow doesn't mess with the modulo
|
std::vector<WebMessage> queue = std::vector<WebMessage>(64); // power of 2 so that overflow doesn't mess with the modulo
|
||||||
size_t readIndex = 0;
|
size_t readIndex = 0;
|
||||||
std::atomic_flag resetQueue = ATOMIC_FLAG_INIT;
|
std::atomic_flag resetQueue = ATOMIC_FLAG_INIT;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user