1
0

Compare commits

..

No commits in common. "124fd4592a2cd0714298cc0594e59423f21b3d37" and "97efa774aaabe9d801c4b82b33e030202049bb1c" have entirely different histories.

6 changed files with 206 additions and 294 deletions

View File

@ -28,14 +28,13 @@ struct CrunchSTFX : public BaseEffect {
static constexpr int oversampleHalfLatency = 16; static constexpr int oversampleHalfLatency = 16;
static constexpr Sample autoGainLevel = 0.1; static constexpr Sample autoGainLevel = 0.1;
const bool autoGain; ParamRange drive{4};
ParamRange drive{stfx::units::dbToGain(autoGain ? 24 : 12)};
ParamRange fuzz{0}; ParamRange fuzz{0};
ParamRange toneHz{2000}; ParamRange toneHz{2000};
ParamRange cutHz{50}; ParamRange cutHz{50};
ParamRange outGain{1}; ParamRange outGain{1};
const bool autoGain;
CrunchSTFX(bool autoGain=true) : autoGain(autoGain) {} CrunchSTFX(bool autoGain=true) : autoGain(autoGain) {}
template<class Storage> template<class Storage>

View File

@ -8,7 +8,7 @@
#include <optional> #include <optional>
#include "../param-info.h" #include "../param-info.h"
#include "../storage/stfx-storage.h" #include "../storage/storage.h"
#include "../ui/web-ui.h" #include "../ui/web-ui.h"
namespace stfx { namespace clap { namespace stfx { namespace clap {
@ -133,7 +133,6 @@ template<template<class> class EffectSTFX>
struct Plugin : public clap_plugin { struct Plugin : public clap_plugin {
const Plugins &plugins; const Plugins &plugins;
const clap_host *host; const clap_host *host;
const clap_host_params *hostParams = nullptr;
const clap_host_webview1 *hostWebview1 = nullptr; const clap_host_webview1 *hostWebview1 = nullptr;
using Effect = stfx::web::WebUILibraryEffect<float, EffectSTFX>; using Effect = stfx::web::WebUILibraryEffect<float, EffectSTFX>;
Effect effect; Effect effect;
@ -153,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();
} }
@ -198,7 +207,6 @@ struct Plugin : public clap_plugin {
auto &plugin = *(Plugin *)obj; auto &plugin = *(Plugin *)obj;
#define STFX_GET_EXT(field, extId) \ #define STFX_GET_EXT(field, extId) \
plugin.field = (decltype(plugin.field))plugin.host->get_extension(plugin.host, extId); plugin.field = (decltype(plugin.field))plugin.host->get_extension(plugin.host, extId);
STFX_GET_EXT(hostParams, CLAP_EXT_PARAMS)
STFX_GET_EXT(hostWebview1, CLAP_EXT_WEBVIEW1) STFX_GET_EXT(hostWebview1, CLAP_EXT_WEBVIEW1)
#undef STFX_GET_EXT #undef STFX_GET_EXT
return true; return true;
@ -311,10 +319,11 @@ 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);
} }
plugin.sendParamEvents(process->out_events);
return CLAP_PROCESS_CONTINUE; return CLAP_PROCESS_CONTINUE;
} }
static void plugin_on_main_thread(const clap_plugin *obj) { static void plugin_on_main_thread(const clap_plugin *obj) {
@ -329,13 +338,6 @@ struct Plugin : public clap_plugin {
typename Effect::ParamStepped *steppedParam = nullptr; typename Effect::ParamStepped *steppedParam = nullptr;
std::optional<SteppedParamInfo> steppedInfo; std::optional<SteppedParamInfo> steppedInfo;
std::atomic_flag hostValueSent = ATOMIC_FLAG_INIT;
std::atomic_flag hostStartGestureSent = ATOMIC_FLAG_INIT;
std::atomic_flag hostStopGestureSent = ATOMIC_FLAG_INIT;
Param() {}
Param(Param &&other) : clap_param_info(other), rangeParam(other.rangeParam), rangeInfo(std::move(other.rangeInfo)), steppedParam(other.steppedParam), steppedInfo(std::move(other.steppedInfo)) {}
void setClapInfo() { void setClapInfo() {
flags |= CLAP_PARAM_IS_AUTOMATABLE; flags |= CLAP_PARAM_IS_AUTOMATABLE;
cookie = this; cookie = this;
@ -352,22 +354,33 @@ struct Plugin : public clap_plugin {
max_value = steppedInfo->high; max_value = steppedInfo->high;
default_value = steppedInfo->defaultValue; default_value = steppedInfo->defaultValue;
} }
hostValueSent.test_and_set();
hostStartGestureSent.test_and_set();
hostStopGestureSent.test_and_set();
} }
}; };
std::vector<Param> params; std::vector<Param> params;
struct ParamScanner : public storage::STFXStorageScanner<ParamScanner> { struct ParamScanner {
std::vector<Param> &params; std::vector<Param> &params;
Crc32 crc; Crc32 crc;
ParamScanner(std::vector<Param> &params) : params(params) {} // Do nothing for most types
template<class V>
void operator()(const char *key, V &v) {
LOG_EXPR(key);
if constexpr (std::is_void_v<decltype(v.state(*this))>) {
LOG_EXPR("recursing");
ParamScanner recurse{*this};
recurse.crc.addString(key, true);
v.state(recurse);
}
}
// Extra methods we add for STFX storage
void info(const char *, const char *) {}
int version(int v) {
return v;
}
template<class PR> template<class PR>
RangeParamInfo & range(const char *key, PR &param) { RangeParamInfo & range(const char *key, PR &param) {
param.context.index = params.size(); // so we can find it in the listeners below 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,90 +389,25 @@ struct Plugin : public clap_plugin {
} }
template<class PS> template<class PS>
SteppedParamInfo & stepped(const char *key, PS &param) { SteppedParamInfo & stepped(const char *key, PS &param) {
param.context.index = params.size(); // so we can find it in the listeners below 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();
entry.steppedParam = &param; entry.steppedParam = &param;
return entry.steppedInfo.emplace(param); return entry.steppedInfo.emplace(param);
} }
template<class V>
bool changed(const char *key, V &v) {
(*this)(key, v);
return false;
}
void invalidate(const char *) {}
}; };
void scanParams() { void scanParams() {
params.clear(); params.resize(0);
ParamScanner scanner{params}; ParamScanner scanner{params};
effect.state(scanner); effect.state(scanner);
for (auto &entry : params) entry.setClapInfo(); for (auto &entry : params) entry.setClapInfo();
// Listen for changes from the effect UI
effect.paramListenerRange = [&](stfx::web::ParamContext context, double value){
params[context.index].hostValueSent.clear();
if (hostParams) hostParams->request_flush(host);
};
effect.paramListenerGesture = [&](stfx::web::ParamContext context, bool gesture){
if (gesture) {
params[context.index].hostStartGestureSent.clear();
} else {
params[context.index].hostStopGestureSent.clear();
}
if (hostParams) hostParams->request_flush(host);
};
effect.paramListenerStepped = [&](stfx::web::ParamContext context, int value){
params[context.index].hostValueSent.clear();
if (hostParams) hostParams->request_flush(host);
};
}
void sendParamEvents(const clap_output_events *events) {
for (auto &param : params) {
auto sendGesture = [&](uint16_t eventType){
clap_event_param_gesture event{
.header={
.size=sizeof(event),
.time=0,
.space_id=CLAP_CORE_EVENT_SPACE_ID,
.type=eventType
},
.param_id=param.id,
};
LOG_EXPR(event.header.type);
// Not *super* bothered if this fails
events->try_push(events, &event.header);
};
if (!param.hostStartGestureSent.test_and_set()) {
sendGesture(CLAP_EVENT_PARAM_GESTURE_BEGIN);
}
if (!param.hostValueSent.test_and_set()) {
clap_event_param_value event{
.header={
.size=sizeof(event),
.time=0,
.space_id=CLAP_CORE_EVENT_SPACE_ID,
.type=CLAP_EVENT_PARAM_VALUE
},
.param_id=param.id,
.cookie=param.cookie,
.note_id=-1,
.port_index=-1,
.channel=-1,
.key=-1
};
LOG_EXPR(event.header.type);
if (param.rangeParam) {
event.value = param.rangeInfo->toUnit((double)*param.rangeParam);
} else {
event.value = (int)*param.steppedParam;
}
if (!events->try_push(events, &event.header)) {
// failed, try again later
param.hostValueSent.clear();
continue;
}
}
if (!param.hostStopGestureSent.test_and_set()) {
sendGesture(CLAP_EVENT_PARAM_GESTURE_END);
}
}
} }
// CLAP parameter methods // CLAP parameter methods
@ -482,7 +430,6 @@ struct Plugin : public clap_plugin {
} else { } else {
*value = (int)*param.steppedParam; *value = (int)*param.steppedParam;
} }
param.hostValueSent.test_and_set();
return true; return true;
} }
} }
@ -526,7 +473,6 @@ struct Plugin : public clap_plugin {
auto *header = inEvents->get(inEvents, i); auto *header = inEvents->get(inEvents, i);
plugin.processEvent(header); plugin.processEvent(header);
} }
plugin.sendParamEvents(outEvents);
} }
// CLAP audio port methods // CLAP audio port methods
@ -612,18 +558,79 @@ struct Plugin : public clap_plugin {
} }
} }
struct StateWriter : public stfx::storage::StorageCborWriter<true, StateWriter> {
using stfx::storage::StorageCborWriter<true, StateWriter>::StorageCborWriter;
void info(const char *, const char *) {}
int version(int v) {
(*this)("version", v);
return v;
}
template<class RP>
RangeParamIgnore range(const char *key, RP &param) {
double v = param;
(*this)(key, v);
return {};
}
template<class SP>
SteppedParamIgnore stepped(const char *key, SP &param) {
int v = param;
(*this)(key, v);
return {};
}
template<class V>
bool changed(const char *key, V &v) {
(*this)(key, v);
return false;
}
void invalidate(const char *) {}
};
struct StateReader : public stfx::storage::StorageCborReader<true, StateReader> {
using stfx::storage::StorageCborReader<true, StateReader>::StorageCborReader;
void info(const char *, const char *) {}
int version(int v) {
(*this)("version", v);
return v;
}
template<class RP>
RangeParamIgnore range(const char *key, RP &param) {
double v = param;
(*this)(key, v);
param = v;
return {};
}
template<class SP>
SteppedParamIgnore stepped(const char *key, SP &param) {
int v = param;
(*this)(key, v);
param = v;
return {};
}
template<class V>
bool changed(const char *key, V &v) {
V prev = v;
(*this)(key, v);
return v == prev;
}
void invalidate(const char *) {}
};
std::vector<unsigned char> stateBuffer; std::vector<unsigned char> stateBuffer;
static bool state_save(const clap_plugin *obj, const clap_ostream *stream) { static bool state_save(const clap_plugin *obj, const clap_ostream *stream) {
auto &plugin = *(Plugin *)obj; auto &plugin = *(Plugin *)obj;
auto &buffer = plugin.stateBuffer; auto &buffer = plugin.stateBuffer;
plugin.effect.saveState(buffer); StateWriter storage{buffer};
plugin.effect.state(storage);
return writeToStream(buffer.data(), buffer.size(), stream); return writeToStream(buffer.data(), buffer.size(), stream);
} }
static bool state_load(const clap_plugin *obj, const clap_istream *stream) { static bool state_load(const clap_plugin *obj, const clap_istream *stream) {
auto &plugin = *(Plugin *)obj; auto &plugin = *(Plugin *)obj;
auto &buffer = plugin.stateBuffer; auto &buffer = plugin.stateBuffer;
if (!fillFromStream(stream, buffer)) return false; if (!fillFromStream(stream, buffer)) return false;
plugin.effect.loadState(buffer); StateReader storage{buffer};
plugin.effect.state(storage);
plugin.sendWebMessages(); // should already be on the main thread plugin.sendWebMessages(); // should already be on the main thread
return true; return true;
} }

View File

@ -1,136 +0,0 @@
#pragma once
#include "./storage.h"
namespace stfx { namespace storage {
template<class SubClassCRTP>
struct STFXStorageScanner : public signalsmith::storage::StorageScanner<SubClassCRTP> {
void info(const char *, const char *) {}
int version(int v) {
return v;
}
template<class V>
bool changed(const char *key, V &v) {
auto &sub = *(SubClassCRTP *)this;
sub(key, v);
return false;
}
void invalidate(const char *) {}
bool extra() {
return false;
}
template<class V>
void extra(const char *, V) {}
template<class PR>
RangeParamIgnore range(const char *key, PR &param) {
return {};
}
template<class PS>
SteppedParamIgnore stepped(const char *key, PS &param) {
return {};
}
};
template<class SubClassCRTP=void>
struct STFXStorageWriter : public signalsmith::storage::StorageCborWriter<SubClassCRTP> {
using signalsmith::storage::StorageCborWriter<SubClassCRTP>::StorageCborWriter;
void info(const char *name, const char *desc) {
sub().extra("name", name);
sub().extra("desc", desc);
}
template<class V>
bool changed(const char *key, V &v) {
sub()(key, v);
return false;
}
void invalidate(const char *) {}
int version(int v) {
sub()("version", v);
return v;
}
bool extra() {
return false;
}
template<class V>
void extra(const char *key, V v) {}
template<class PR>
RangeParamIgnore range(const char *key, PR &param) {
sub()(key, param);
return {};
}
template<class PS>
SteppedParamIgnore stepped(const char *key, PS &param) {
sub()(key, param);
return {};
}
private:
SubClassCRTP & sub() {
return *(SubClassCRTP *)this;
}
};
template<class SubClassCRTP=void>
struct STFXStorageReader : public signalsmith::storage::StorageCborReader<SubClassCRTP> {
using signalsmith::storage::StorageCborReader<SubClassCRTP>::StorageCborReader;
// This is supplemental
void info(const char *, const char *) {}
int version(int v) {
sub()("version", v);
return v;
}
bool extra() {
return false;
}
template<class V>
void extra(const char *key, V v) {}
template<class V>
bool changed(const char *key, V &v) {
V prev = v;
sub()(key, v);
return v != prev;
}
void invalidate(const char *invalidatedKey) {
LOG_EXPR(invalidatedKey);
abort();
}
template<class PR>
RangeParamIgnore range(const char *key, PR &param) {
sub()(key, param);
return {};
}
template<class PS>
SteppedParamIgnore stepped(const char *key, PS &param) {
sub()(key, param);
return {};
}
private:
SubClassCRTP & sub() {
return *(SubClassCRTP *)this;
}
};
// If void, use itself for CRTP
template<>
struct STFXStorageWriter<void> : public STFXStorageWriter<STFXStorageWriter<void>> {
using STFXStorageWriter<STFXStorageWriter<void>>::STFXStorageWriter;
};
template<>
struct STFXStorageReader<void> : public STFXStorageReader<STFXStorageReader<void>> {
using STFXStorageReader<STFXStorageReader<void>>::STFXStorageReader;
};
}} // namespace

View File

@ -2,18 +2,14 @@
#include "./cbor-walker.h" #include "./cbor-walker.h"
#include <string> namespace stfx { namespace storage {
#include <vector>
namespace signalsmith { namespace storage {
struct StorageDummy { struct StorageDummy {
template<class V> template<class V>
void operator()(const char *, V &) {} void operator()(const char *, V &) {}
}; };
template<class SubClassCRTP> struct StorageExample {
struct StorageScanner {
template<class V> template<class V>
void operator()(const char */*key*/, V &v) { void operator()(const char */*key*/, V &v) {
value(v); value(v);
@ -31,7 +27,6 @@ private:
void value(float &v) {}; void value(float &v) {};
void value(double &v) {}; void value(double &v) {};
void value(bool &v) {}; void value(bool &v) {};
void value(std::string &str) {};
template<class Item> template<class Item>
void value(std::vector<Item> &array) { void value(std::vector<Item> &array) {
@ -42,17 +37,33 @@ private:
template<class V> template<class V>
void value(V &v) { void value(V &v) {
v.state(*(SubClassCRTP *)this); v.state(*this);
} }
}; };
template<class SubClassCRTP=void> namespace _impl {
template<class Fallback, class MaybeVoid=void>
struct VoidFallback {
using T = MaybeVoid;
};
template<class Fallback>
struct VoidFallback<Fallback, void> {
using T = Fallback;
};
}
template<bool compact=false, class SubClassCRTP=void>
struct StorageCborWriter { struct StorageCborWriter {
StorageCborWriter(const signalsmith::cbor::CborWriter &writer, std::vector<unsigned char> *buffer=nullptr) : cbor(writer) { StorageCborWriter(const signalsmith::cbor::CborWriter &writer, std::vector<unsigned char> *buffer=nullptr) : cbor(writer) {
if (buffer) buffer->resize(0); if (buffer) buffer->resize(0);
if (compact) {
cbor.addTag(0xCB68ADED); // turns into "2store..." when base64-encoded
cbor.openArray();
} else {
cbor.openMap(); cbor.openMap();
} }
}
StorageCborWriter(std::vector<unsigned char> &cborBuffer) : StorageCborWriter(signalsmith::cbor::CborWriter(cborBuffer), &cborBuffer) {} StorageCborWriter(std::vector<unsigned char> &cborBuffer) : StorageCborWriter(signalsmith::cbor::CborWriter(cborBuffer), &cborBuffer) {}
~StorageCborWriter() { ~StorageCborWriter() {
cbor.close(); cbor.close();
@ -60,7 +71,7 @@ struct StorageCborWriter {
template<class V> template<class V>
void operator()(const char *key, V &value) { void operator()(const char *key, V &value) {
cbor.addUtf8(key); if (!compact) cbor.addUtf8(key);
writeValue(value); writeValue(value);
} }
@ -91,7 +102,6 @@ private:
cbor.addBool(value); cbor.addBool(value);
} }
// Support writing C strings (but not reading them), so inherited writers can add supplemental hints/info without allocating
void writeValue(const char *cStr) { void writeValue(const char *cStr) {
cbor.addUtf8(cStr); cbor.addUtf8(cStr);
} }
@ -123,34 +133,40 @@ private:
STORAGE_TYPED_ARRAY(double) STORAGE_TYPED_ARRAY(double)
#undef STORAGE_TYPED_ARRAY #undef STORAGE_TYPED_ARRAY
using SubStorage = typename _impl::VoidFallback<StorageCborWriter, SubClassCRTP>::T;
template<class Obj> template<class Obj>
void writeValue(Obj &obj) { void writeValue(Obj &obj) {
cbor.openMap(); cbor.openMap();
obj.state(*(SubClassCRTP *)this); obj.state(*(SubStorage *)this);
cbor.close(); cbor.close();
} }
}; };
template<class SubClassCRTP=void> template<bool compact=false, class SubClassCRTP=void>
struct StorageCborReader { struct StorageCborReader {
using Cbor = signalsmith::cbor::TaggedCborWalker; using Cbor = signalsmith::cbor::TaggedCborWalker;
StorageCborReader(Cbor c) : cbor(c) { StorageCborReader(Cbor c) : cbor(c) {
if (compact) {
if (cbor.isArray()) cbor = cbor.enter();
} else {
if (cbor.isMap()) cbor = cbor.enter(); if (cbor.isMap()) cbor = cbor.enter();
} }
}
StorageCborReader(const std::vector<unsigned char> &v) : StorageCborReader(Cbor(v)) {} StorageCborReader(const std::vector<unsigned char> &v) : StorageCborReader(Cbor(v)) {}
template<class V> template<class V>
void operator()(const char *key, V &v) { void operator()(const char *key, V &v) {
if (filterKeyBytes != nullptr) { if (filterKeyBytes != nullptr) {
if (!keyMatch(key, filterKeyBytes, filterKeyLength)) return; if (!keyMatch(key, filterKeyBytes, filterKeyLength)) return;
} } else if (!compact) {
if (!cbor.isUtf8()) return; // We expect a string key if (!cbor.isUtf8()) return; // We expect a string key
// If we have a filter, we *should* be just in front of the appropriate key, but check anyway
if (!keyMatch(key, (const char *)cbor.bytes(), cbor.length())) { if (!keyMatch(key, (const char *)cbor.bytes(), cbor.length())) {
return; // key doesn't match return; // key doesn't match
} }
cbor++; cbor++;
}
readValue(v); readValue(v);
} }
@ -161,6 +177,11 @@ private:
template<class Obj> template<class Obj>
void readValue(Obj &obj) { void readValue(Obj &obj) {
if (compact) {
// No keys, no filters
return obj.state(*(SubClass *)this);
}
if (!cbor.isMap()) return; if (!cbor.isMap()) return;
cbor = cbor.forEachPair([&](Cbor key, Cbor value){ cbor = cbor.forEachPair([&](Cbor key, Cbor value){
@ -172,8 +193,8 @@ private:
// Temporarily set key, and scan the object for that property // Temporarily set key, and scan the object for that property
filterKeyBytes = (const char *)key.bytes(); filterKeyBytes = (const char *)key.bytes();
filterKeyLength = key.length(); filterKeyLength = key.length();
cbor = key; cbor = value;
obj.state(*(SubClassCRTP *)this); obj.state(*(SubClass *)this);
filterKeyBytes = fkb; filterKeyBytes = fkb;
filterKeyLength = fkl; filterKeyLength = fkl;
@ -198,9 +219,7 @@ private:
#undef STORAGE_BASIC_TYPE #undef STORAGE_BASIC_TYPE
void readValue(std::string &v) { void readValue(std::string &v) {
if (!cbor.isUtf8()) v.clear(); v = (cbor++).utf8();
v.assign((const char *)cbor.bytes(), cbor.length());
++cbor;
} }
template<class Item> template<class Item>
@ -244,16 +263,8 @@ private:
} }
return key[filterKeyLength] == 0; return key[filterKeyLength] == 0;
} }
};
// If void, use itself for CRTP using SubClass = typename _impl::VoidFallback<StorageCborReader, SubClassCRTP>::T;
template<>
struct StorageCborWriter<void> : public StorageCborWriter<StorageCborWriter<void>> {
using StorageCborWriter<StorageCborWriter<void>>::StorageCborWriter;
};
template<>
struct StorageCborReader<void> : public StorageCborReader<StorageCborReader<void>> {
using StorageCborReader<StorageCborReader<void>>::StorageCborReader;
}; };
}} // namespace }} // namespace

View File

@ -9,7 +9,7 @@
body { body {
display: grid; display: grid;
grid-template-areas: "header" "params"; grid-template-areas: "header" "params";
grid-template-rows: min-content 1fr; grid-template-rows: 3em 1fr;
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -113,6 +113,7 @@
data.gesture = true; data.gesture = true;
data._gestureUnit = data.rangeUnit; data._gestureUnit = data.rangeUnit;
if (count == 2) { if (count == 2) {
console.log("data.gesture", data.gesture);
data.value = data.defaultValue; data.value = data.defaultValue;
data.gesture = false; data.gesture = false;
} }

View File

@ -21,40 +21,80 @@ struct ParamContext {
ParamContext() : index(0) {} ParamContext() : index(0) {}
}; };
struct WebStateWriter : public storage::STFXStorageWriter<WebStateWriter> { struct WebStateWriter : public storage::StorageCborWriter<false, WebStateWriter> {
using Super = storage::STFXStorageWriter<WebStateWriter>; using Super = storage::StorageCborWriter<false, WebStateWriter>;
using Super::Super; using Super::Super;
// There should never be a version mismatch, ignore this void info(const char *name, const char *desc) {
int version(int v) {return v;} extra("name", name);
extra("desc", desc);
}
int version(int v) {
return v; // we don't use old states, so not worth tracking
}
// Include extra info
bool extra() { bool extra() {
return true; return true;
} }
template<class V> template<class V>
void extra(const char *key, V v) { void extra(const char *key, V v) {
(*this)(key, v); (*this)(key, v);
} }
template<class PR>
RangeParamIgnore range(const char *key, PR &param) {
(*this)(key, param);
return {};
}
template<class PS>
SteppedParamIgnore stepped(const char *key, PS &param) {
(*this)(key, param);
return {};
}
template<class V>
bool changed(const char *key, V &v) {
(*this)(key, v);
return false;
}
void invalidate(const char *) {}
}; };
struct WebStateReader : public storage::STFXStorageReader<WebStateReader> { struct WebStateReader : public storage::StorageCborReader<false, WebStateReader> {
using Super = storage::STFXStorageReader<WebStateReader>; using Super = storage::StorageCborReader<false, WebStateReader>;
using Super::Super; using Super::Super;
// There should never be a version mismatch, ignore this void info(const char *, const char *) {}
int version(int v) {return v;} int version(int v) {return v;}
// We're interested in an extended set of values being sent back
bool extra() { bool extra() {
// We're interested in an extended set of values being sent back, even if we ignore actual "extra" things
return true; return true;
} }
// But anything read-only gets skipped
template<class V> template<class V>
void extra(const char *key, V v) {} void extra(const char *key, V v) {}
template<class PR>
RangeParamIgnore range(const char *key, PR &param) {
(*this)(key, param);
return {};
}
template<class PS>
SteppedParamIgnore stepped(const char *key, PS &param) {
(*this)(key, param);
return {};
}
template<class V>
bool changed(const char *key, V &v) {
V prev = v;
(*this)(key, v);
return v != prev;
}
void invalidate(const char *invalidatedKey) { void invalidate(const char *invalidatedKey) {
LOG_EXPR(invalidatedKey); LOG_EXPR(invalidatedKey);
} }
@ -295,16 +335,6 @@ struct WebUIHelper {
std::function<void(ParamContext, bool)> paramListenerGesture; std::function<void(ParamContext, bool)> paramListenerGesture;
std::function<void(ParamContext, int)> paramListenerStepped; std::function<void(ParamContext, int)> paramListenerStepped;
void saveState(std::vector<unsigned char> &bytes) {
stfx::storage::STFXStorageWriter<> storage(bytes);
this->state(storage);
}
void loadState(const std::vector<unsigned char> &bytes) {
stfx::storage::STFXStorageReader<> storage(bytes);
this->state(storage);
requestEntireState();
}
struct WebMessage { struct WebMessage {
std::vector<unsigned char> bytes; std::vector<unsigned char> bytes;