Send events on parameter gesture/change
This commit is contained in:
parent
4e646da32b
commit
124fd4592a
@ -133,6 +133,7 @@ template<template<class> class EffectSTFX>
|
||||
struct Plugin : public clap_plugin {
|
||||
const Plugins &plugins;
|
||||
const clap_host *host;
|
||||
const clap_host_params *hostParams = nullptr;
|
||||
const clap_host_webview1 *hostWebview1 = nullptr;
|
||||
using Effect = stfx::web::WebUILibraryEffect<float, EffectSTFX>;
|
||||
Effect effect;
|
||||
@ -152,16 +153,6 @@ struct Plugin : public clap_plugin {
|
||||
this->get_extension = plugin_get_extension;
|
||||
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();
|
||||
}
|
||||
|
||||
@ -207,6 +198,7 @@ struct Plugin : public clap_plugin {
|
||||
auto &plugin = *(Plugin *)obj;
|
||||
#define STFX_GET_EXT(field, 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)
|
||||
#undef STFX_GET_EXT
|
||||
return true;
|
||||
@ -319,11 +311,10 @@ struct Plugin : public clap_plugin {
|
||||
inputBuffers.resize(0);
|
||||
outputBuffers.resize(0);
|
||||
|
||||
plugin.host->request_callback(plugin.host);
|
||||
|
||||
if (plugin.effect.hasPendingWebMessage()) {
|
||||
plugin.host->request_callback(plugin.host);
|
||||
}
|
||||
plugin.sendParamEvents(process->out_events);
|
||||
return CLAP_PROCESS_CONTINUE;
|
||||
}
|
||||
static void plugin_on_main_thread(const clap_plugin *obj) {
|
||||
@ -337,6 +328,13 @@ plugin.host->request_callback(plugin.host);
|
||||
std::optional<RangeParamInfo> rangeInfo;
|
||||
typename Effect::ParamStepped *steppedParam = nullptr;
|
||||
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() {
|
||||
flags |= CLAP_PARAM_IS_AUTOMATABLE;
|
||||
@ -354,6 +352,10 @@ plugin.host->request_callback(plugin.host);
|
||||
max_value = steppedInfo->high;
|
||||
default_value = steppedInfo->defaultValue;
|
||||
}
|
||||
|
||||
hostValueSent.test_and_set();
|
||||
hostStartGestureSent.test_and_set();
|
||||
hostStopGestureSent.test_and_set();
|
||||
}
|
||||
};
|
||||
std::vector<Param> params;
|
||||
@ -365,7 +367,7 @@ plugin.host->request_callback(plugin.host);
|
||||
|
||||
template<class PR>
|
||||
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();
|
||||
auto &entry = params.back();
|
||||
entry.id = crc.copy().addString(key, true).done();
|
||||
@ -374,7 +376,8 @@ plugin.host->request_callback(plugin.host);
|
||||
}
|
||||
template<class PS>
|
||||
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();
|
||||
auto &entry = params.back();
|
||||
entry.id = crc.copy().addString(key, true).done();
|
||||
@ -383,10 +386,80 @@ plugin.host->request_callback(plugin.host);
|
||||
}
|
||||
};
|
||||
void scanParams() {
|
||||
params.resize(0);
|
||||
params.clear();
|
||||
ParamScanner scanner{params};
|
||||
effect.state(scanner);
|
||||
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
|
||||
@ -409,6 +482,7 @@ plugin.host->request_callback(plugin.host);
|
||||
} else {
|
||||
*value = (int)*param.steppedParam;
|
||||
}
|
||||
param.hostValueSent.test_and_set();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -452,6 +526,7 @@ plugin.host->request_callback(plugin.host);
|
||||
auto *header = inEvents->get(inEvents, i);
|
||||
plugin.processEvent(header);
|
||||
}
|
||||
plugin.sendParamEvents(outEvents);
|
||||
}
|
||||
|
||||
// CLAP audio port methods
|
||||
@ -536,60 +611,19 @@ plugin.host->request_callback(plugin.host);
|
||||
chunkSize = buffer.size();
|
||||
}
|
||||
}
|
||||
|
||||
struct StateWriter : public stfx::storage::STFXStorageWriter<StateWriter> {
|
||||
using stfx::storage::STFXStorageWriter<StateWriter>::STFXStorageWriter;
|
||||
|
||||
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 {};
|
||||
}
|
||||
};
|
||||
struct StateReader : public stfx::storage::STFXStorageReader<StateReader> {
|
||||
using stfx::storage::STFXStorageReader<StateReader>::STFXStorageReader;
|
||||
|
||||
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 {};
|
||||
}
|
||||
void invalidate(const char *) {
|
||||
LOG_EXPR("invalidate() called on state load - this should mark the entire state as dirty for reload");
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<unsigned char> stateBuffer;
|
||||
static bool state_save(const clap_plugin *obj, const clap_ostream *stream) {
|
||||
auto &plugin = *(Plugin *)obj;
|
||||
auto &buffer = plugin.stateBuffer;
|
||||
StateWriter storage{buffer};
|
||||
plugin.effect.state(storage);
|
||||
plugin.effect.saveState(buffer);
|
||||
return writeToStream(buffer.data(), buffer.size(), stream);
|
||||
}
|
||||
static bool state_load(const clap_plugin *obj, const clap_istream *stream) {
|
||||
auto &plugin = *(Plugin *)obj;
|
||||
auto &buffer = plugin.stateBuffer;
|
||||
if (!fillFromStream(stream, buffer)) return false;
|
||||
StateReader storage{buffer};
|
||||
plugin.effect.state(storage);
|
||||
|
||||
plugin.effect.loadState(buffer);
|
||||
plugin.sendWebMessages(); // should already be on the main thread
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -142,23 +142,16 @@ struct StorageCborReader {
|
||||
|
||||
template<class V>
|
||||
void operator()(const char *key, V &v) {
|
||||
LOG_EXPR(key);
|
||||
LOG_EXPR(filterKeyLength);
|
||||
if (filterKeyBytes != nullptr) {
|
||||
if (!keyMatch(key, filterKeyBytes, filterKeyLength)) return;
|
||||
}
|
||||
LOG_EXPR(cbor.isUtf8());
|
||||
if (!cbor.isUtf8()) return; // We expect a string key
|
||||
LOG_EXPR(cbor.utf8());
|
||||
// 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())) {
|
||||
LOG_EXPR(key);
|
||||
LOG_EXPR(cbor.utf8());
|
||||
return; // key doesn't match
|
||||
}
|
||||
cbor++;
|
||||
readValue(v);
|
||||
LOG_EXPR(v);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-areas: "header" "params";
|
||||
grid-template-rows: 3em 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@ -159,7 +159,6 @@ struct WebUIHelper {
|
||||
SuperRange::state(storage);
|
||||
|
||||
constexpr bool isWeb = storageIsWeb<Storage>();
|
||||
LOG_EXPR(isWeb);
|
||||
if (isWeb) { // a bunch of extra state
|
||||
storage.extra("$type", "ParamRange");
|
||||
storage.extra("name", info->name);
|
||||
@ -171,9 +170,7 @@ struct WebUIHelper {
|
||||
storage("gesture", gesture);
|
||||
|
||||
double unit = info->toUnit(*this);
|
||||
LOG_EXPR(unit);
|
||||
if (storage.changed("rangeUnit", unit)) {
|
||||
LOG_EXPR(unit);
|
||||
SuperRange::operator=(info->fromUnit(unit));
|
||||
storage.invalidate("value");
|
||||
}
|
||||
@ -297,6 +294,16 @@ struct WebUIHelper {
|
||||
std::function<void(ParamContext, double)> paramListenerRange;
|
||||
std::function<void(ParamContext, bool)> paramListenerGesture;
|
||||
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 {
|
||||
std::vector<unsigned char> bytes;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user