Compare commits
2 Commits
97efa774aa
...
124fd4592a
| Author | SHA1 | Date | |
|---|---|---|---|
| 124fd4592a | |||
| 4e646da32b |
5
crunch.h
5
crunch.h
@ -28,13 +28,14 @@ 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;
|
||||||
|
|
||||||
ParamRange drive{4};
|
const bool autoGain;
|
||||||
|
|
||||||
|
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>
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "../param-info.h"
|
#include "../param-info.h"
|
||||||
#include "../storage/storage.h"
|
#include "../storage/stfx-storage.h"
|
||||||
#include "../ui/web-ui.h"
|
#include "../ui/web-ui.h"
|
||||||
|
|
||||||
namespace stfx { namespace clap {
|
namespace stfx { namespace clap {
|
||||||
@ -133,6 +133,7 @@ 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;
|
||||||
@ -152,16 +153,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +198,7 @@ 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;
|
||||||
@ -319,11 +311,10 @@ 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) {
|
||||||
@ -337,6 +328,13 @@ plugin.host->request_callback(plugin.host);
|
|||||||
std::optional<RangeParamInfo> rangeInfo;
|
std::optional<RangeParamInfo> rangeInfo;
|
||||||
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;
|
||||||
@ -354,33 +352,22 @@ plugin.host->request_callback(plugin.host);
|
|||||||
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 {
|
struct ParamScanner : public storage::STFXStorageScanner<ParamScanner> {
|
||||||
std::vector<Param> ¶ms;
|
std::vector<Param> ¶ms;
|
||||||
Crc32 crc;
|
Crc32 crc;
|
||||||
|
|
||||||
// Do nothing for most types
|
ParamScanner(std::vector<Param> ¶ms) : params(params) {}
|
||||||
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 ¶m) {
|
RangeParamInfo & range(const char *key, PR ¶m) {
|
||||||
param.context.index = params.size();
|
param.context.index = params.size(); // so we can find it in the listeners below
|
||||||
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();
|
||||||
@ -389,25 +376,90 @@ plugin.host->request_callback(plugin.host);
|
|||||||
}
|
}
|
||||||
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();
|
param.context.index = params.size(); // so we can find it in the listeners below
|
||||||
|
|
||||||
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 = ¶m;
|
entry.steppedParam = ¶m;
|
||||||
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.resize(0);
|
params.clear();
|
||||||
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 ¶m : 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
|
||||||
@ -430,6 +482,7 @@ plugin.host->request_callback(plugin.host);
|
|||||||
} else {
|
} else {
|
||||||
*value = (int)*param.steppedParam;
|
*value = (int)*param.steppedParam;
|
||||||
}
|
}
|
||||||
|
param.hostValueSent.test_and_set();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -473,6 +526,7 @@ plugin.host->request_callback(plugin.host);
|
|||||||
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
|
||||||
@ -557,80 +611,19 @@ plugin.host->request_callback(plugin.host);
|
|||||||
chunkSize = buffer.size();
|
chunkSize = buffer.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ¶m) {
|
|
||||||
double v = param;
|
|
||||||
(*this)(key, v);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
template<class SP>
|
|
||||||
SteppedParamIgnore stepped(const char *key, SP ¶m) {
|
|
||||||
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 ¶m) {
|
|
||||||
double v = param;
|
|
||||||
(*this)(key, v);
|
|
||||||
param = v;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
template<class SP>
|
|
||||||
SteppedParamIgnore stepped(const char *key, SP ¶m) {
|
|
||||||
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;
|
||||||
StateWriter storage{buffer};
|
plugin.effect.saveState(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;
|
||||||
StateReader storage{buffer};
|
plugin.effect.loadState(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;
|
||||||
}
|
}
|
||||||
|
|||||||
136
stfx/storage/stfx-storage.h
Normal file
136
stfx/storage/stfx-storage.h
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#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 ¶m) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
||||||
|
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 ¶m) {
|
||||||
|
sub()(key, param);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
||||||
|
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 ¶m) {
|
||||||
|
sub()(key, param);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
||||||
|
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
|
||||||
@ -2,14 +2,18 @@
|
|||||||
|
|
||||||
#include "./cbor-walker.h"
|
#include "./cbor-walker.h"
|
||||||
|
|
||||||
namespace stfx { namespace storage {
|
#include <string>
|
||||||
|
#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 &) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StorageExample {
|
template<class SubClassCRTP>
|
||||||
|
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);
|
||||||
@ -27,6 +31,7 @@ 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) {
|
||||||
@ -37,32 +42,16 @@ private:
|
|||||||
|
|
||||||
template<class V>
|
template<class V>
|
||||||
void value(V &v) {
|
void value(V &v) {
|
||||||
v.state(*this);
|
v.state(*(SubClassCRTP *)this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace _impl {
|
template<class SubClassCRTP=void>
|
||||||
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.openMap();
|
||||||
cbor.addTag(0xCB68ADED); // turns into "2store..." when base64-encoded
|
|
||||||
cbor.openArray();
|
|
||||||
} else {
|
|
||||||
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() {
|
||||||
@ -71,7 +60,7 @@ struct StorageCborWriter {
|
|||||||
|
|
||||||
template<class V>
|
template<class V>
|
||||||
void operator()(const char *key, V &value) {
|
void operator()(const char *key, V &value) {
|
||||||
if (!compact) cbor.addUtf8(key);
|
cbor.addUtf8(key);
|
||||||
writeValue(value);
|
writeValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +91,7 @@ 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);
|
||||||
}
|
}
|
||||||
@ -133,26 +123,20 @@ 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(*(SubStorage *)this);
|
obj.state(*(SubClassCRTP *)this);
|
||||||
cbor.close();
|
cbor.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<bool compact=false, class SubClassCRTP=void>
|
template<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.isMap()) cbor = cbor.enter();
|
||||||
if (cbor.isArray()) cbor = cbor.enter();
|
|
||||||
} else {
|
|
||||||
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)) {}
|
||||||
|
|
||||||
@ -160,13 +144,13 @@ struct StorageCborReader {
|
|||||||
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 (!keyMatch(key, (const char *)cbor.bytes(), cbor.length())) {
|
|
||||||
return; // key doesn't match
|
|
||||||
}
|
|
||||||
cbor++;
|
|
||||||
}
|
}
|
||||||
|
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())) {
|
||||||
|
return; // key doesn't match
|
||||||
|
}
|
||||||
|
cbor++;
|
||||||
readValue(v);
|
readValue(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,11 +161,6 @@ 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){
|
||||||
@ -193,8 +172,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 = value;
|
cbor = key;
|
||||||
obj.state(*(SubClass *)this);
|
obj.state(*(SubClassCRTP *)this);
|
||||||
|
|
||||||
filterKeyBytes = fkb;
|
filterKeyBytes = fkb;
|
||||||
filterKeyLength = fkl;
|
filterKeyLength = fkl;
|
||||||
@ -219,7 +198,9 @@ private:
|
|||||||
#undef STORAGE_BASIC_TYPE
|
#undef STORAGE_BASIC_TYPE
|
||||||
|
|
||||||
void readValue(std::string &v) {
|
void readValue(std::string &v) {
|
||||||
v = (cbor++).utf8();
|
if (!cbor.isUtf8()) v.clear();
|
||||||
|
v.assign((const char *)cbor.bytes(), cbor.length());
|
||||||
|
++cbor;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Item>
|
template<class Item>
|
||||||
@ -263,8 +244,16 @@ private:
|
|||||||
}
|
}
|
||||||
return key[filterKeyLength] == 0;
|
return key[filterKeyLength] == 0;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
using SubClass = typename _impl::VoidFallback<StorageCborReader, SubClassCRTP>::T;
|
|
||||||
|
// If void, use itself for CRTP
|
||||||
|
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
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
body {
|
body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "header" "params";
|
grid-template-areas: "header" "params";
|
||||||
grid-template-rows: 3em 1fr;
|
grid-template-rows: min-content 1fr;
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -113,7 +113,6 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,80 +21,40 @@ struct ParamContext {
|
|||||||
ParamContext() : index(0) {}
|
ParamContext() : index(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebStateWriter : public storage::StorageCborWriter<false, WebStateWriter> {
|
struct WebStateWriter : public storage::STFXStorageWriter<WebStateWriter> {
|
||||||
using Super = storage::StorageCborWriter<false, WebStateWriter>;
|
using Super = storage::STFXStorageWriter<WebStateWriter>;
|
||||||
|
|
||||||
using Super::Super;
|
using Super::Super;
|
||||||
|
|
||||||
void info(const char *name, const char *desc) {
|
// There should never be a version mismatch, ignore this
|
||||||
extra("name", name);
|
int version(int v) {return v;}
|
||||||
extra("desc", desc);
|
|
||||||
}
|
// Include extra info
|
||||||
|
|
||||||
int version(int v) {
|
|
||||||
return v; // we don't use old states, so not worth tracking
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ¶m) {
|
|
||||||
(*this)(key, param);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
template<class PS>
|
|
||||||
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
|
||||||
(*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::StorageCborReader<false, WebStateReader> {
|
struct WebStateReader : public storage::STFXStorageReader<WebStateReader> {
|
||||||
using Super = storage::StorageCborReader<false, WebStateReader>;
|
using Super = storage::STFXStorageReader<WebStateReader>;
|
||||||
|
|
||||||
using Super::Super;
|
using Super::Super;
|
||||||
|
|
||||||
void info(const char *, const char *) {}
|
// There should never be a version mismatch, ignore this
|
||||||
|
|
||||||
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 ¶m) {
|
|
||||||
(*this)(key, param);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
template<class PS>
|
|
||||||
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
|
||||||
(*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);
|
||||||
}
|
}
|
||||||
@ -334,6 +294,16 @@ struct WebUIHelper {
|
|||||||
std::function<void(ParamContext, double)> paramListenerRange;
|
std::function<void(ParamContext, double)> paramListenerRange;
|
||||||
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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user