Compare commits
2 Commits
bbad24674a
...
16dc595aa6
| Author | SHA1 | Date | |
|---|---|---|---|
| 16dc595aa6 | |||
| f1ede7a2ca |
@ -308,9 +308,16 @@ struct Plugin : public clap_plugin {
|
||||
}
|
||||
inputBuffers.resize(0);
|
||||
outputBuffers.resize(0);
|
||||
|
||||
if (plugin.effect.hasPendingWebMessage()) {
|
||||
plugin.host->request_callback(plugin.host);
|
||||
}
|
||||
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
|
||||
struct Param : public clap_param_info {
|
||||
@ -609,6 +616,7 @@ struct Plugin : public clap_plugin {
|
||||
if (!fillFromStream(stream, buffer)) return false;
|
||||
StateReader storage{buffer};
|
||||
plugin.effect.state(storage);
|
||||
plugin.sendWebMessages(); // should already be on the main thread
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -630,9 +638,7 @@ struct Plugin : public clap_plugin {
|
||||
void sendWebMessages() {
|
||||
if (!hostWebview1) return;
|
||||
while (auto *m = effect.getPendingWebMessage()) {
|
||||
LOG_EXPR("pending web message");
|
||||
hostWebview1->send(host, m->bytes.data(), m->bytes.size());
|
||||
LOG_EXPR(m->bytes.size());
|
||||
m->sent();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,29 +3,145 @@
|
||||
<head>
|
||||
<title>Generic STFX UI</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-areas: "header" "params";
|
||||
grid-template-rows: 3em 1fr;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
background: linear-gradient(#FFF, #EEE, #CCC);
|
||||
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;
|
||||
|
||||
/* no text selectable by default */
|
||||
user-select: none;
|
||||
}
|
||||
output {
|
||||
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{name}</h1>
|
||||
<ul>
|
||||
<h1 id="header">{name}</h1>
|
||||
<section id="params">
|
||||
<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>
|
||||
</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="matsui-bundle.min.js"></script>
|
||||
<script>
|
||||
|
||||
@ -94,14 +94,23 @@ struct WebUIHelper {
|
||||
|
||||
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");
|
||||
}
|
||||
@ -128,6 +137,7 @@ struct WebUIHelper {
|
||||
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);
|
||||
@ -187,6 +197,44 @@ struct WebUIHelper {
|
||||
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>
|
||||
struct WebSTFX : public EffectSTFX<WebBase<Effect>, ExtraArgs...>, MaybeChanged {
|
||||
// Inherit constructor(s)
|
||||
@ -219,6 +267,11 @@ struct WebUIHelper {
|
||||
readyToSend.test_and_set();
|
||||
}
|
||||
};
|
||||
|
||||
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`
|
||||
WebMessage * getPendingWebMessage() {
|
||||
@ -251,19 +304,26 @@ struct WebUIHelper {
|
||||
|
||||
if (cbor.isUtf8()) {
|
||||
if (cbor.utf8View() == "ready") {
|
||||
LOG_EXPR(cbor.utf8View() == "ready");
|
||||
if (auto *m = getEmptyMessage()) {
|
||||
resetQueue.test_and_set();
|
||||
m->markReady();
|
||||
} // if this fails we're doing a reset anyway, so no worrie
|
||||
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 {
|
||||
std::cout << "web effect maybe changed\n";
|
||||
// Resend entire state again
|
||||
if (auto *m = getEmptyMessage()) {
|
||||
resetQueue.test_and_set();
|
||||
m->markReady();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user