Compare commits
2 Commits
bbad24674a
...
16dc595aa6
| Author | SHA1 | Date | |
|---|---|---|---|
| 16dc595aa6 | |||
| f1ede7a2ca |
@ -308,9 +308,16 @@ struct Plugin : public clap_plugin {
|
|||||||
}
|
}
|
||||||
inputBuffers.resize(0);
|
inputBuffers.resize(0);
|
||||||
outputBuffers.resize(0);
|
outputBuffers.resize(0);
|
||||||
|
|
||||||
|
if (plugin.effect.hasPendingWebMessage()) {
|
||||||
|
plugin.host->request_callback(plugin.host);
|
||||||
|
}
|
||||||
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) {
|
||||||
|
auto &plugin = *(Plugin *)obj;
|
||||||
|
plugin.sendWebMessages();
|
||||||
|
}
|
||||||
|
|
||||||
// parameters
|
// parameters
|
||||||
struct Param : public clap_param_info {
|
struct Param : public clap_param_info {
|
||||||
@ -609,6 +616,7 @@ struct Plugin : public clap_plugin {
|
|||||||
if (!fillFromStream(stream, buffer)) return false;
|
if (!fillFromStream(stream, buffer)) return false;
|
||||||
StateReader storage{buffer};
|
StateReader storage{buffer};
|
||||||
plugin.effect.state(storage);
|
plugin.effect.state(storage);
|
||||||
|
plugin.sendWebMessages(); // should already be on the main thread
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,9 +638,7 @@ struct Plugin : public clap_plugin {
|
|||||||
void sendWebMessages() {
|
void sendWebMessages() {
|
||||||
if (!hostWebview1) return;
|
if (!hostWebview1) return;
|
||||||
while (auto *m = effect.getPendingWebMessage()) {
|
while (auto *m = effect.getPendingWebMessage()) {
|
||||||
LOG_EXPR("pending web message");
|
|
||||||
hostWebview1->send(host, m->bytes.data(), m->bytes.size());
|
hostWebview1->send(host, m->bytes.data(), m->bytes.size());
|
||||||
LOG_EXPR(m->bytes.size());
|
|
||||||
m->sent();
|
m->sent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,29 +3,145 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Generic STFX UI</title>
|
<title>Generic STFX UI</title>
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "header" "params";
|
||||||
|
grid-template-rows: 3em 1fr;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
background: linear-gradient(#FFF, #EEE, #CCC);
|
background: linear-gradient(#FFF, #EEE, #CCC);
|
||||||
color: #000;
|
color: #000;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
word-wrap: break-word;
|
||||||
font-family: Bahnschrift, 'DIN Alternate', 'Alte DIN 1451 Mittelschrift', 'D-DIN', 'OpenDin', 'Clear Sans', 'Barlow', 'Abel', 'Franklin Gothic Medium', system-ui, sans-serif;
|
font-family: Bahnschrift, 'DIN Alternate', 'Alte DIN 1451 Mittelschrift', 'D-DIN', 'OpenDin', 'Clear Sans', 'Barlow', 'Abel', 'Franklin Gothic Medium', system-ui, sans-serif;
|
||||||
|
|
||||||
|
/* no text selectable by default */
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
output {
|
output {
|
||||||
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
grid-area: header;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#params {
|
||||||
|
grid-area: params;
|
||||||
|
display: flex;
|
||||||
|
align-content: space-around;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-range {
|
||||||
|
display: grid;
|
||||||
|
width: 3.5rem;
|
||||||
|
grid-template-areas: "dial" "name";
|
||||||
|
grid-template-rows: 3.5rem 1fr;
|
||||||
|
}
|
||||||
|
.param-range-value {
|
||||||
|
position: relative;
|
||||||
|
grid-area: dial;
|
||||||
|
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
}
|
||||||
|
.param-range-dial {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.param-range-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 30%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.param-range-units {
|
||||||
|
position: absolute;
|
||||||
|
top: 60%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.param-range-name {
|
||||||
|
grid-area: name;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{name}</h1>
|
<h1 id="header">{name}</h1>
|
||||||
<ul>
|
<section id="params">
|
||||||
<template @foreach>
|
<template @foreach>
|
||||||
<li @if="${d => d.$type == 'ParamRange'}">{=}</li>
|
<label class="param-range" @if="${d => d.$type == 'ParamRange'}">
|
||||||
|
<div class="param-range-name">{name}</div>
|
||||||
|
<script>
|
||||||
|
function move(data, dx, dy) {
|
||||||
|
data.rangeUnit = Math.min(1, Math.max(0, data.rangeUnit - dy/250));
|
||||||
|
}
|
||||||
|
function press(data, count) {
|
||||||
|
if (count == 2) data.value = data.defaultValue;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="param-range-value" $move="${move}" $press="${press}">
|
||||||
|
<canvas class="param-range-dial" $update="${drawDial}"></canvas>
|
||||||
|
<output class="param-range-text">{text}</output><br>
|
||||||
|
<div class="param-range-units">{textUnits}</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</section>
|
||||||
|
<script>
|
||||||
|
function drawDial(data, canvas) {
|
||||||
|
let scale = window.devicePixelRatio;
|
||||||
|
let pixels = canvas.offsetWidth*scale;
|
||||||
|
if (canvas.width != pixels) canvas.width = pixels;
|
||||||
|
if (canvas.height != pixels) canvas.height = pixels;
|
||||||
|
|
||||||
|
let context = canvas.getContext('2d');
|
||||||
|
context.resetTransform();
|
||||||
|
context.clearRect(0, 0, pixels, pixels);
|
||||||
|
|
||||||
|
context.scale(pixels/2, pixels/2);
|
||||||
|
context.translate(1, 1);
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.ellipse(0, 0, 1, 1, 0, 0, Math.PI*2);
|
||||||
|
context.fillStyle = '#FFF';
|
||||||
|
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.lineCap = 'round';
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="cbor.min.js"></script>
|
<script src="cbor.min.js"></script>
|
||||||
<script src="matsui-bundle.min.js"></script>
|
<script src="matsui-bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -94,14 +94,23 @@ struct WebUIHelper {
|
|||||||
|
|
||||||
if (storage.extra()) {
|
if (storage.extra()) {
|
||||||
storage.extra("$type", "ParamRange");
|
storage.extra("$type", "ParamRange");
|
||||||
|
storage.extra("name", info->name);
|
||||||
|
storage.extra("defaultValue", info->defaultValue);
|
||||||
std::string text, units;
|
std::string text, units;
|
||||||
info->toString((double)*this, text, units);
|
info->toString((double)*this, text, units);
|
||||||
storage.extra("text", text);
|
storage.extra("text", text);
|
||||||
storage.extra("textUnits", units);
|
storage.extra("textUnits", units);
|
||||||
|
|
||||||
|
double unit = info->toUnit(*this);
|
||||||
|
if (storage.changed("rangeUnit", unit)) {
|
||||||
|
*this = info->fromUnit(unit);
|
||||||
|
storage.invalidate("value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevV != *this) {
|
if (prevV != *this) {
|
||||||
effect->maybeChanged();
|
effect->maybeChanged();
|
||||||
|
storage.invalidate("rangeUnit");
|
||||||
storage.invalidate("text");
|
storage.invalidate("text");
|
||||||
storage.invalidate("textUnits");
|
storage.invalidate("textUnits");
|
||||||
}
|
}
|
||||||
@ -128,6 +137,7 @@ struct WebUIHelper {
|
|||||||
SuperStepped::state(storage);
|
SuperStepped::state(storage);
|
||||||
|
|
||||||
if (storage.extra()) {
|
if (storage.extra()) {
|
||||||
|
storage.extra("name", info->name);
|
||||||
storage.extra("$type", "ParamStepped");
|
storage.extra("$type", "ParamStepped");
|
||||||
std::string text = info->toString((int)*this);
|
std::string text = info->toString((int)*this);
|
||||||
storage.extra("text", text);
|
storage.extra("text", text);
|
||||||
@ -187,6 +197,44 @@ struct WebUIHelper {
|
|||||||
void invalidate(const char *) {}
|
void invalidate(const char *) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WebStateReader : public storage::StorageCborReader<false, WebStateReader> {
|
||||||
|
using Super = storage::StorageCborReader<false, WebStateReader>;
|
||||||
|
|
||||||
|
using Super::Super;
|
||||||
|
|
||||||
|
void info(const char *, const char *) {}
|
||||||
|
|
||||||
|
int version(int v) {return v;}
|
||||||
|
|
||||||
|
bool extra() {
|
||||||
|
// We're interested in an extended set of values being sent back, even if we ignore actual "extra" things
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class 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) {
|
||||||
|
LOG_EXPR(invalidatedKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<class Effect, class... ExtraArgs>
|
template<class Effect, class... ExtraArgs>
|
||||||
struct WebSTFX : public EffectSTFX<WebBase<Effect>, ExtraArgs...>, MaybeChanged {
|
struct WebSTFX : public EffectSTFX<WebBase<Effect>, ExtraArgs...>, MaybeChanged {
|
||||||
// Inherit constructor(s)
|
// Inherit constructor(s)
|
||||||
@ -220,6 +268,11 @@ struct WebUIHelper {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool hasPendingWebMessage() const {
|
||||||
|
auto &message = queue[readIndex];
|
||||||
|
return message.readyToSend.test();
|
||||||
|
}
|
||||||
|
|
||||||
// Poll on the main thread (and directly after any `.webReceive()`), calling `.sent()` after you've sent it, repeat until you get `nullptr`
|
// Poll on the main thread (and directly after any `.webReceive()`), calling `.sent()` after you've sent it, repeat until you get `nullptr`
|
||||||
WebMessage * getPendingWebMessage() {
|
WebMessage * getPendingWebMessage() {
|
||||||
auto &message = queue[readIndex];
|
auto &message = queue[readIndex];
|
||||||
@ -251,19 +304,26 @@ struct WebUIHelper {
|
|||||||
|
|
||||||
if (cbor.isUtf8()) {
|
if (cbor.isUtf8()) {
|
||||||
if (cbor.utf8View() == "ready") {
|
if (cbor.utf8View() == "ready") {
|
||||||
LOG_EXPR(cbor.utf8View() == "ready");
|
|
||||||
if (auto *m = getEmptyMessage()) {
|
if (auto *m = getEmptyMessage()) {
|
||||||
resetQueue.test_and_set();
|
resetQueue.test_and_set();
|
||||||
m->markReady();
|
m->markReady();
|
||||||
} // if this fails we're doing a reset anyway, so no worrie
|
} // if this fails we're doing a reset anyway, so no worrie
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (cbor.isMap()) {
|
||||||
|
// Apply it as a merge
|
||||||
|
WebStateReader reader(cbor);
|
||||||
|
this->state(reader);
|
||||||
}
|
}
|
||||||
std::cout << "received " << size << " bytes from webview\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void maybeChanged() override {
|
void maybeChanged() override {
|
||||||
std::cout << "web effect maybe changed\n";
|
std::cout << "web effect maybe changed\n";
|
||||||
|
// Resend entire state again
|
||||||
|
if (auto *m = getEmptyMessage()) {
|
||||||
|
resetQueue.test_and_set();
|
||||||
|
m->markReady();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user