@xsynaptic/sonic-ui 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -20
- package/dist/default/dat.js +51 -0
- package/dist/default/define/button.js +7 -0
- package/dist/default/define/envelope.js +7 -0
- package/dist/default/define/split.js +7 -0
- package/dist/default/define/switch.js +7 -0
- package/dist/default/define/waveform.js +7 -0
- package/dist/default/define/wavestrip.js +7 -0
- package/dist/default/define/xy.js +7 -0
- package/dist/default/define.js +7 -1
- package/dist/default/elements/button.js +165 -0
- package/dist/default/elements/dial.js +16 -22
- package/dist/default/elements/envelope.js +383 -0
- package/dist/default/elements/meter.js +155 -63
- package/dist/default/elements/number.js +22 -9
- package/dist/default/elements/radio-group.js +183 -0
- package/dist/default/elements/readout-claim.js +81 -0
- package/dist/default/elements/readout.js +35 -0
- package/dist/default/elements/segmented.js +9 -159
- package/dist/default/elements/slider.js +42 -51
- package/dist/default/elements/sonic-element.js +69 -31
- package/dist/default/elements/split.js +249 -0
- package/dist/default/elements/switch.js +273 -0
- package/dist/default/elements/value-element.js +694 -0
- package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
- package/dist/default/elements/wave-element.js +80 -0
- package/dist/default/elements/waveform.js +328 -0
- package/dist/default/elements/wavestrip.js +185 -0
- package/dist/default/elements/xy.js +487 -0
- package/dist/default/index.js +10 -2
- package/dist/default/lib/adsr-shape.js +113 -0
- package/dist/default/lib/ballistics.js +55 -0
- package/dist/default/lib/canvas-surface.js +203 -0
- package/dist/default/lib/copy-node.js +32 -2
- package/dist/default/lib/crossfade.js +22 -0
- package/dist/default/lib/distribute.js +215 -0
- package/dist/default/lib/drag-step.js +99 -0
- package/dist/default/lib/envelope-curve.js +9 -0
- package/dist/default/lib/field.js +94 -0
- package/dist/default/lib/focus-by-pointer.js +28 -0
- package/dist/default/lib/frame-timeline.js +63 -0
- package/dist/default/lib/hold.js +38 -0
- package/dist/default/lib/marker-band.js +80 -0
- package/dist/default/lib/marker-rider.js +120 -0
- package/dist/default/lib/math.js +17 -3
- package/dist/default/lib/modifier-press.js +11 -0
- package/dist/default/lib/number-list.js +6 -6
- package/dist/default/lib/pointer-drag.js +46 -0
- package/dist/default/lib/render.js +20 -5
- package/dist/default/lib/resample-peaks.js +46 -0
- package/dist/default/lib/strip-scene.js +48 -0
- package/dist/default/lib/taper.js +23 -17
- package/dist/default/lib/time-regions.js +8 -0
- package/dist/default/lib/tracking-clock.js +35 -0
- package/dist/default/lib/value-mapping.js +115 -0
- package/dist/default/lib/waveform-buckets.js +41 -0
- package/dist/default/lib/write-attribute.js +1 -0
- package/dist/dev/dat.d.ts +19 -0
- package/dist/dev/dat.js +52 -0
- package/dist/dev/dat.js.map +1 -0
- package/dist/dev/define/button.d.ts +2 -0
- package/dist/dev/define/button.js +8 -0
- package/dist/dev/define/button.js.map +1 -0
- package/dist/dev/define/dial.js.map +1 -1
- package/dist/dev/define/envelope.d.ts +2 -0
- package/dist/dev/define/envelope.js +8 -0
- package/dist/dev/define/envelope.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -1
- package/dist/dev/define/number.js.map +1 -1
- package/dist/dev/define/segmented.js.map +1 -1
- package/dist/dev/define/slider.js.map +1 -1
- package/dist/dev/define/split.d.ts +2 -0
- package/dist/dev/define/split.js +8 -0
- package/dist/dev/define/split.js.map +1 -0
- package/dist/dev/define/switch.d.ts +2 -0
- package/dist/dev/define/switch.js +8 -0
- package/dist/dev/define/switch.js.map +1 -0
- package/dist/dev/define/waveform.d.ts +2 -0
- package/dist/dev/define/waveform.js +8 -0
- package/dist/dev/define/waveform.js.map +1 -0
- package/dist/dev/define/wavestrip.d.ts +2 -0
- package/dist/dev/define/wavestrip.js +8 -0
- package/dist/dev/define/wavestrip.js.map +1 -0
- package/dist/dev/define/xy.d.ts +2 -0
- package/dist/dev/define/xy.js +8 -0
- package/dist/dev/define/xy.js.map +1 -0
- package/dist/dev/define.d.ts +15 -3
- package/dist/dev/define.js +7 -1
- package/dist/dev/elements/{key.d.ts → button.d.ts} +8 -6
- package/dist/dev/elements/button.js +166 -0
- package/dist/dev/elements/button.js.map +1 -0
- package/dist/dev/elements/dial.d.ts +6 -4
- package/dist/dev/elements/dial.js +16 -22
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +36 -0
- package/dist/dev/elements/envelope.js +388 -0
- package/dist/dev/elements/envelope.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +1 -1
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +11 -6
- package/dist/dev/elements/meter.js +155 -63
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +7 -3
- package/dist/dev/elements/number.js +22 -9
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +25 -0
- package/dist/dev/elements/radio-group.js +184 -0
- package/dist/dev/elements/radio-group.js.map +1 -0
- package/dist/dev/elements/readout-claim.js +82 -0
- package/dist/dev/elements/readout-claim.js.map +1 -0
- package/dist/dev/elements/readout.js +36 -0
- package/dist/dev/elements/readout.js.map +1 -0
- package/dist/dev/elements/segmented.d.ts +4 -10
- package/dist/dev/elements/segmented.js +9 -159
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +6 -6
- package/dist/dev/elements/slider.js +42 -51
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +6 -3
- package/dist/dev/elements/sonic-element.js +119 -40
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.d.ts +21 -0
- package/dist/dev/elements/split.js +258 -0
- package/dist/dev/elements/split.js.map +1 -0
- package/dist/dev/elements/switch.d.ts +35 -0
- package/dist/dev/elements/switch.js +274 -0
- package/dist/dev/elements/switch.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +84 -0
- package/dist/dev/elements/value-element.js +695 -0
- package/dist/dev/elements/value-element.js.map +1 -0
- package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
- package/dist/dev/elements/value-entry.js.map +1 -0
- package/dist/dev/elements/wave-element.d.ts +37 -0
- package/dist/dev/elements/wave-element.js +81 -0
- package/dist/dev/elements/wave-element.js.map +1 -0
- package/dist/dev/elements/waveform.d.ts +55 -0
- package/dist/dev/elements/waveform.js +329 -0
- package/dist/dev/elements/waveform.js.map +1 -0
- package/dist/dev/elements/wavestrip.d.ts +51 -0
- package/dist/dev/elements/wavestrip.js +186 -0
- package/dist/dev/elements/wavestrip.js.map +1 -0
- package/dist/dev/elements/xy.d.ts +62 -0
- package/dist/dev/elements/xy.js +488 -0
- package/dist/dev/elements/xy.js.map +1 -0
- package/dist/dev/index.d.ts +11 -2
- package/dist/dev/index.js +10 -2
- package/dist/dev/lib/adsr-shape.js +114 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -0
- package/dist/dev/lib/ballistics.js +56 -0
- package/dist/dev/lib/ballistics.js.map +1 -0
- package/dist/dev/lib/canvas-surface.d.ts +26 -0
- package/dist/dev/lib/canvas-surface.js +204 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -0
- package/dist/dev/lib/copy-node.js +32 -2
- package/dist/dev/lib/copy-node.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +12 -0
- package/dist/dev/lib/crossfade.js +23 -0
- package/dist/dev/lib/crossfade.js.map +1 -0
- package/dist/dev/lib/distribute.d.ts +4 -0
- package/dist/dev/lib/distribute.js +216 -0
- package/dist/dev/lib/distribute.js.map +1 -0
- package/dist/dev/lib/drag-step.js +100 -0
- package/dist/dev/lib/drag-step.js.map +1 -0
- package/dist/dev/lib/envelope-curve.d.ts +4 -0
- package/dist/dev/lib/envelope-curve.js +10 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -0
- package/dist/dev/lib/field.d.ts +4 -0
- package/dist/dev/lib/field.js +95 -0
- package/dist/dev/lib/field.js.map +1 -0
- package/dist/dev/lib/focus-by-pointer.js +29 -0
- package/dist/dev/lib/focus-by-pointer.js.map +1 -0
- package/dist/dev/lib/frame-timeline.js +64 -0
- package/dist/dev/lib/frame-timeline.js.map +1 -0
- package/dist/dev/lib/hold.js +39 -0
- package/dist/dev/lib/hold.js.map +1 -0
- package/dist/dev/lib/marker-band.js +81 -0
- package/dist/dev/lib/marker-band.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +121 -0
- package/dist/dev/lib/marker-rider.js.map +1 -0
- package/dist/dev/lib/math.js +17 -3
- package/dist/dev/lib/math.js.map +1 -1
- package/dist/dev/lib/modifier-press.js +12 -0
- package/dist/dev/lib/modifier-press.js.map +1 -0
- package/dist/dev/lib/number-list.js +6 -6
- package/dist/dev/lib/number-list.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +47 -0
- package/dist/dev/lib/pointer-drag.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/render.js +20 -5
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +47 -0
- package/dist/dev/lib/resample-peaks.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +49 -0
- package/dist/dev/lib/strip-scene.js.map +1 -0
- package/dist/dev/lib/taper.js +23 -17
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/dev/lib/time-regions.d.ts +4 -0
- package/dist/dev/lib/time-regions.js +9 -0
- package/dist/dev/lib/time-regions.js.map +1 -0
- package/dist/dev/lib/tracking-clock.js +36 -0
- package/dist/dev/lib/tracking-clock.js.map +1 -0
- package/dist/dev/lib/value-mapping.d.ts +23 -0
- package/dist/dev/lib/value-mapping.js +116 -0
- package/dist/dev/lib/value-mapping.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +9 -0
- package/dist/dev/lib/waveform-buckets.js +42 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -0
- package/dist/dev/lib/write-attribute.js +1 -0
- package/dist/dev/lib/write-attribute.js.map +1 -1
- package/dist/skins/amber.css +8 -0
- package/dist/skins/flat.css +25 -0
- package/dist/skins/ivory.css +2 -2
- package/dist/skins/lime.css +2 -0
- package/dist/skins/slate.css +2 -0
- package/dist/styles/button.css +240 -0
- package/dist/styles/controls.css +8 -1
- package/dist/styles/dial.css +110 -56
- package/dist/styles/envelope.css +204 -0
- package/dist/styles/led.css +39 -8
- package/dist/styles/material.css +343 -100
- package/dist/styles/meter.css +41 -52
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +61 -0
- package/dist/styles/ring.css +74 -0
- package/dist/styles/segmented.css +46 -51
- package/dist/styles/slider.css +177 -40
- package/dist/styles/switch.css +386 -0
- package/dist/styles/waveform.css +196 -0
- package/dist/styles/wavestrip.css +232 -0
- package/dist/styles/xy.css +241 -0
- package/package.json +15 -6
- package/dist/default/define/key.js +0 -7
- package/dist/default/elements/key.js +0 -169
- package/dist/default/elements/range-element.js +0 -546
- package/dist/default/lib/detent.js +0 -17
- package/dist/dev/define/key.d.ts +0 -2
- package/dist/dev/define/key.js +0 -8
- package/dist/dev/define/key.js.map +0 -1
- package/dist/dev/elements/key.js +0 -170
- package/dist/dev/elements/key.js.map +0 -1
- package/dist/dev/elements/range-element.d.ts +0 -64
- package/dist/dev/elements/range-element.js +0 -547
- package/dist/dev/elements/range-element.js.map +0 -1
- package/dist/dev/elements/range-entry.js.map +0 -1
- package/dist/dev/lib/detent.js +0 -18
- package/dist/dev/lib/detent.js.map +0 -1
- package/dist/styles/key.css +0 -242
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { requireChild, template } from "../lib/render.js";
|
|
2
|
+
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
|
+
import { SonicElement } from "./sonic-element.js";
|
|
4
|
+
import { ReadoutClaim } from "./readout-claim.js";
|
|
5
|
+
import { Readout } from "./readout.js";
|
|
6
|
+
import { dragThresholdPx } from "../lib/drag-step.js";
|
|
7
|
+
import { bindDrag } from "../lib/pointer-drag.js";
|
|
8
|
+
import { SonicValueElement, linkValue } from "./value-element.js";
|
|
9
|
+
import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
|
|
10
|
+
import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
|
|
11
|
+
|
|
12
|
+
//#region src/elements/envelope.ts
|
|
13
|
+
const curveStages = [
|
|
14
|
+
"attack",
|
|
15
|
+
"decay",
|
|
16
|
+
"release"
|
|
17
|
+
];
|
|
18
|
+
const bindable = [
|
|
19
|
+
...adsrStages,
|
|
20
|
+
"attack-curve",
|
|
21
|
+
"decay-curve",
|
|
22
|
+
"release-curve"
|
|
23
|
+
];
|
|
24
|
+
const bindableNames = new Set(bindable);
|
|
25
|
+
function flip(point, isFlipped) {
|
|
26
|
+
return isFlipped ? {
|
|
27
|
+
...point,
|
|
28
|
+
y: -point.y
|
|
29
|
+
} : point;
|
|
30
|
+
}
|
|
31
|
+
function isParent(node) {
|
|
32
|
+
return "querySelector" in node;
|
|
33
|
+
}
|
|
34
|
+
const emptyPath = {
|
|
35
|
+
fill: "",
|
|
36
|
+
floor: {
|
|
37
|
+
from: 0,
|
|
38
|
+
to: 0
|
|
39
|
+
},
|
|
40
|
+
stroke: ""
|
|
41
|
+
};
|
|
42
|
+
function stageParts(envelope, hook, stages) {
|
|
43
|
+
return new Map(stages.map((stage) => [stage, requireChild(envelope, `.${hook}[data-sonic-stage="${stage}"]`, HTMLDivElement)]));
|
|
44
|
+
}
|
|
45
|
+
const renderEnvelope = template(`
|
|
46
|
+
<div class="sonic-envelope" aria-hidden="true">
|
|
47
|
+
<svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
|
|
48
|
+
<path class="sonic-envelope-fill" />
|
|
49
|
+
<rect class="sonic-envelope-fill sonic-envelope-floor" y="1" />
|
|
50
|
+
<path class="sonic-envelope-line" />
|
|
51
|
+
</svg>
|
|
52
|
+
<div class="sonic-envelope-handle" data-sonic-stage="delay" hidden></div>
|
|
53
|
+
<div class="sonic-envelope-handle" data-sonic-stage="attack" hidden></div>
|
|
54
|
+
<div class="sonic-envelope-handle" data-sonic-stage="hold" hidden></div>
|
|
55
|
+
<div class="sonic-envelope-handle" data-sonic-stage="decay" hidden></div>
|
|
56
|
+
<div class="sonic-envelope-handle" data-sonic-stage="release" hidden></div>
|
|
57
|
+
<div class="sonic-envelope-curve" data-sonic-stage="attack" hidden></div>
|
|
58
|
+
<div class="sonic-envelope-curve" data-sonic-stage="decay" hidden></div>
|
|
59
|
+
<div class="sonic-envelope-curve" data-sonic-stage="release" hidden></div>
|
|
60
|
+
<div class="sonic-envelope-readout" popover="manual"><span></span></div>
|
|
61
|
+
</div>
|
|
62
|
+
`, HTMLDivElement);
|
|
63
|
+
var SonicEnvelope = class extends SonicElement {
|
|
64
|
+
static observedAttributes = [
|
|
65
|
+
...SonicElement.observedAttributes,
|
|
66
|
+
...bindable,
|
|
67
|
+
"readout"
|
|
68
|
+
];
|
|
69
|
+
get attack() {
|
|
70
|
+
return this.getAttribute("attack") ?? "";
|
|
71
|
+
}
|
|
72
|
+
set attack(id) {
|
|
73
|
+
this.reflect("attack", id);
|
|
74
|
+
}
|
|
75
|
+
get attackCurve() {
|
|
76
|
+
return this.getAttribute("attack-curve") ?? "";
|
|
77
|
+
}
|
|
78
|
+
set attackCurve(id) {
|
|
79
|
+
this.reflect("attack-curve", id);
|
|
80
|
+
}
|
|
81
|
+
get decay() {
|
|
82
|
+
return this.getAttribute("decay") ?? "";
|
|
83
|
+
}
|
|
84
|
+
set decay(id) {
|
|
85
|
+
this.reflect("decay", id);
|
|
86
|
+
}
|
|
87
|
+
get decayCurve() {
|
|
88
|
+
return this.getAttribute("decay-curve") ?? "";
|
|
89
|
+
}
|
|
90
|
+
set decayCurve(id) {
|
|
91
|
+
this.reflect("decay-curve", id);
|
|
92
|
+
}
|
|
93
|
+
get delay() {
|
|
94
|
+
return this.getAttribute("delay") ?? "";
|
|
95
|
+
}
|
|
96
|
+
set delay(id) {
|
|
97
|
+
this.reflect("delay", id);
|
|
98
|
+
}
|
|
99
|
+
get hold() {
|
|
100
|
+
return this.getAttribute("hold") ?? "";
|
|
101
|
+
}
|
|
102
|
+
set hold(id) {
|
|
103
|
+
this.reflect("hold", id);
|
|
104
|
+
}
|
|
105
|
+
get readout() {
|
|
106
|
+
return this.hasAttribute("readout");
|
|
107
|
+
}
|
|
108
|
+
set readout(isOn) {
|
|
109
|
+
this.reflect("readout", isOn);
|
|
110
|
+
}
|
|
111
|
+
get release() {
|
|
112
|
+
return this.getAttribute("release") ?? "";
|
|
113
|
+
}
|
|
114
|
+
set release(id) {
|
|
115
|
+
this.reflect("release", id);
|
|
116
|
+
}
|
|
117
|
+
get releaseCurve() {
|
|
118
|
+
return this.getAttribute("release-curve") ?? "";
|
|
119
|
+
}
|
|
120
|
+
set releaseCurve(id) {
|
|
121
|
+
this.reflect("release-curve", id);
|
|
122
|
+
}
|
|
123
|
+
get sustain() {
|
|
124
|
+
return this.getAttribute("sustain") ?? "";
|
|
125
|
+
}
|
|
126
|
+
set sustain(id) {
|
|
127
|
+
this.reflect("sustain", id);
|
|
128
|
+
}
|
|
129
|
+
#bindings = /* @__PURE__ */ new Map();
|
|
130
|
+
#claim = new ReadoutClaim(() => {
|
|
131
|
+
this.#renderReadout();
|
|
132
|
+
});
|
|
133
|
+
#envelope = renderEnvelope();
|
|
134
|
+
#curveHandles = stageParts(this.#envelope, "sonic-envelope-curve", curveStages);
|
|
135
|
+
#fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
|
|
136
|
+
#floor = requireChild(this.#envelope, ".sonic-envelope-floor", SVGElement);
|
|
137
|
+
#graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
|
|
138
|
+
#handles = stageParts(this.#envelope, "sonic-envelope-handle", timeStages);
|
|
139
|
+
#isDriving = false;
|
|
140
|
+
#line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
|
|
141
|
+
#pointerDrag;
|
|
142
|
+
#readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
|
|
143
|
+
#shape = adsrShape({});
|
|
144
|
+
#signal;
|
|
145
|
+
#waiting = /* @__PURE__ */ new Set();
|
|
146
|
+
#warned = /* @__PURE__ */ new Set();
|
|
147
|
+
attributeChangedCallback(name) {
|
|
148
|
+
if (!this.#signal) return;
|
|
149
|
+
if (name === "disabled") {
|
|
150
|
+
this.#pointerDrag?.end();
|
|
151
|
+
this.#draw();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (bindableNames.has(name)) this.#bind();
|
|
155
|
+
else this.#renderReadout();
|
|
156
|
+
}
|
|
157
|
+
connectedCallback() {
|
|
158
|
+
this.upgradeProperties(...adsrStages, "attackCurve", "decayCurve", "readout", "releaseCurve");
|
|
159
|
+
super.connectedCallback();
|
|
160
|
+
}
|
|
161
|
+
connect(signal) {
|
|
162
|
+
const envelope = this.#envelope;
|
|
163
|
+
this.#signal = signal;
|
|
164
|
+
this.keepControl(envelope, signal);
|
|
165
|
+
this.#bind();
|
|
166
|
+
this.checkStyles(envelope, "envelope.css");
|
|
167
|
+
this.#bindPointer(envelope, signal);
|
|
168
|
+
signal.addEventListener("abort", () => {
|
|
169
|
+
this.#signal = void 0;
|
|
170
|
+
this.#unbind();
|
|
171
|
+
}, { once: true });
|
|
172
|
+
}
|
|
173
|
+
#bind() {
|
|
174
|
+
this.#pointerDrag?.end();
|
|
175
|
+
this.#unbind();
|
|
176
|
+
for (const name of bindable) {
|
|
177
|
+
const element = this.#resolve(name);
|
|
178
|
+
if (!element) continue;
|
|
179
|
+
const link = linkValue(element);
|
|
180
|
+
const unwatch = link.watch(() => {
|
|
181
|
+
if (!this.#isDriving) this.#draw();
|
|
182
|
+
});
|
|
183
|
+
this.#bindings.set(name, {
|
|
184
|
+
element,
|
|
185
|
+
link,
|
|
186
|
+
unwatch
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
this.#draw();
|
|
190
|
+
}
|
|
191
|
+
#bindPointer(envelope, signal) {
|
|
192
|
+
this.#pointerDrag = bindDrag(envelope, {
|
|
193
|
+
cancel: (drag) => {
|
|
194
|
+
for (const { binding, from } of drag.drives) binding.link.input(from);
|
|
195
|
+
},
|
|
196
|
+
grab: (event) => this.#grab(event),
|
|
197
|
+
move: (drag, event) => {
|
|
198
|
+
this.#moveDrag(drag, event);
|
|
199
|
+
},
|
|
200
|
+
release: (drag) => {
|
|
201
|
+
this.#claim.press(false);
|
|
202
|
+
for (const { binding, from } of drag.drives) {
|
|
203
|
+
if (binding.link.value() === from) continue;
|
|
204
|
+
binding.element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
toggle: (isDragging) => {
|
|
208
|
+
this.toggleState("dragging", isDragging);
|
|
209
|
+
}
|
|
210
|
+
}, signal);
|
|
211
|
+
}
|
|
212
|
+
#curveDrives(stage) {
|
|
213
|
+
const binding = this.#bindings.get(`${stage}-curve`);
|
|
214
|
+
return binding ? [{
|
|
215
|
+
axis: "y",
|
|
216
|
+
binding
|
|
217
|
+
}] : [];
|
|
218
|
+
}
|
|
219
|
+
#draw() {
|
|
220
|
+
const shape = this.#readShape();
|
|
221
|
+
const path = this.#bindings.size === 0 ? emptyPath : adsrPath(shape);
|
|
222
|
+
this.#shape = shape;
|
|
223
|
+
writeAttribute(this.#line, "d", path.stroke);
|
|
224
|
+
writeAttribute(this.#fill, "d", path.fill);
|
|
225
|
+
writeAttribute(this.#floor, "x", String(path.floor.from));
|
|
226
|
+
writeAttribute(this.#floor, "width", String(path.floor.to - path.floor.from));
|
|
227
|
+
for (const [stage, part] of this.#handles) this.#drawPoint(part, {
|
|
228
|
+
at: shape.handles.find((point) => point.stage === stage),
|
|
229
|
+
drives: this.#drives(stage)
|
|
230
|
+
});
|
|
231
|
+
for (const [stage, part] of this.#curveHandles) this.#drawPoint(part, {
|
|
232
|
+
at: shape.curveHandles.find((point) => point.stage === stage),
|
|
233
|
+
drives: this.#curveDrives(stage)
|
|
234
|
+
});
|
|
235
|
+
this.#renderReadout();
|
|
236
|
+
}
|
|
237
|
+
#drawPoint(part, point) {
|
|
238
|
+
const { at, drives } = point;
|
|
239
|
+
writeAttribute(part, "hidden", at ? void 0 : "");
|
|
240
|
+
if (!at) return;
|
|
241
|
+
const isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());
|
|
242
|
+
part.style.setProperty("--_sonic-envelope-x", String(at.x));
|
|
243
|
+
part.style.setProperty("--_sonic-envelope-y", String(at.y));
|
|
244
|
+
writeAttribute(part, "data-sonic-axes", drives.map(({ axis }) => axis).join(""));
|
|
245
|
+
writeAttribute(part, "data-sonic-disabled", isStill ? "" : void 0);
|
|
246
|
+
}
|
|
247
|
+
#drives(stage) {
|
|
248
|
+
const x = this.#bindings.get(stage);
|
|
249
|
+
const y = stage === "decay" ? this.#bindings.get("sustain") : void 0;
|
|
250
|
+
return [...x ? [{
|
|
251
|
+
axis: "x",
|
|
252
|
+
binding: x
|
|
253
|
+
}] : [], ...y ? [{
|
|
254
|
+
axis: "y",
|
|
255
|
+
binding: y
|
|
256
|
+
}] : []];
|
|
257
|
+
}
|
|
258
|
+
#grab(event) {
|
|
259
|
+
const pressed = this.#pressed(event);
|
|
260
|
+
if (!pressed || pressed.drives.length === 0) return void 0;
|
|
261
|
+
const { drives, isFlipped, part } = pressed;
|
|
262
|
+
this.#claim.press(true);
|
|
263
|
+
const box = this.#graph.getBoundingClientRect();
|
|
264
|
+
const travel = {
|
|
265
|
+
x: Math.max(1, box.width * this.#shape.share),
|
|
266
|
+
y: Math.max(1, box.height)
|
|
267
|
+
};
|
|
268
|
+
const start = (axis) => {
|
|
269
|
+
const link = drives.find((drive) => drive.axis === axis)?.binding.link;
|
|
270
|
+
if (!link) return;
|
|
271
|
+
const mapping = link.mapping();
|
|
272
|
+
const from = link.value();
|
|
273
|
+
return {
|
|
274
|
+
from,
|
|
275
|
+
mapping,
|
|
276
|
+
proportion: mapping.proportionOf(from),
|
|
277
|
+
travelPx: travel[axis]
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
return {
|
|
281
|
+
drives: drives.map((drive) => ({
|
|
282
|
+
...drive,
|
|
283
|
+
from: drive.binding.link.value()
|
|
284
|
+
})),
|
|
285
|
+
isFlipped,
|
|
286
|
+
part,
|
|
287
|
+
state: startFieldDrag({
|
|
288
|
+
position: flip(pointerPosition(event), isFlipped),
|
|
289
|
+
thresholdPx: dragThresholdPx(event.pointerType),
|
|
290
|
+
x: start("x"),
|
|
291
|
+
y: start("y")
|
|
292
|
+
})
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
#moveDrag(drag, event) {
|
|
296
|
+
const mappings = {};
|
|
297
|
+
for (const { axis, binding } of drag.drives) mappings[axis] = binding.link.mapping();
|
|
298
|
+
const step = stepFieldDrag(mappings, drag.state, flip(pointerMove(event), drag.isFlipped));
|
|
299
|
+
drag.state = step.state;
|
|
300
|
+
if (step.state.isEngaged) this.#claim.reveal("drag");
|
|
301
|
+
this.#isDriving = true;
|
|
302
|
+
for (const { axis, binding } of drag.drives) {
|
|
303
|
+
const next = step[axis];
|
|
304
|
+
if (next !== void 0) binding.link.input(next);
|
|
305
|
+
}
|
|
306
|
+
this.#isDriving = false;
|
|
307
|
+
this.#draw();
|
|
308
|
+
}
|
|
309
|
+
#pressed(event) {
|
|
310
|
+
const part = event.target instanceof Element ? event.target.closest(".sonic-envelope-handle, .sonic-envelope-curve") : void 0;
|
|
311
|
+
const stage = part?.dataset.sonicStage;
|
|
312
|
+
if (!part || stage === void 0 || this.isDisabled()) return void 0;
|
|
313
|
+
const isCurveHandle = part.classList.contains("sonic-envelope-curve");
|
|
314
|
+
return {
|
|
315
|
+
drives: (isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage)).filter(({ binding }) => !binding.link.isDisabled()),
|
|
316
|
+
isFlipped: isCurveHandle && stage === "attack",
|
|
317
|
+
part
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
#readShape() {
|
|
321
|
+
const proportions = {};
|
|
322
|
+
const curves = {};
|
|
323
|
+
for (const stage of adsrStages) {
|
|
324
|
+
const link = this.#bindings.get(stage)?.link;
|
|
325
|
+
proportions[stage] = link?.mapping().proportionOf(link.value());
|
|
326
|
+
}
|
|
327
|
+
for (const stage of curveStages) curves[stage] = this.#bindings.get(`${stage}-curve`)?.link.value();
|
|
328
|
+
return adsrShape(proportions, curves);
|
|
329
|
+
}
|
|
330
|
+
#rebindOnce(key, arrives) {
|
|
331
|
+
if (this.#waiting.has(key)) return;
|
|
332
|
+
this.#waiting.add(key);
|
|
333
|
+
arrives(() => {
|
|
334
|
+
this.#waiting.delete(key);
|
|
335
|
+
if (this.#signal) this.#bind();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
#renderReadout() {
|
|
339
|
+
const drag = this.#pointerDrag?.current();
|
|
340
|
+
if (!drag) {
|
|
341
|
+
this.#readout.close();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.#readout.show({
|
|
345
|
+
anchor: drag.part,
|
|
346
|
+
isOpen: this.#claim.isRevealed && this.readout,
|
|
347
|
+
text: drag.drives.map(({ binding }) => binding.element.valueText).join(", ")
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
#resolve(name) {
|
|
351
|
+
const id = this.getAttribute(name);
|
|
352
|
+
const root = this.getRootNode();
|
|
353
|
+
if (!id || !isParent(root)) return void 0;
|
|
354
|
+
const found = root.querySelector(`#${CSS.escape(id)}`);
|
|
355
|
+
if (found instanceof SonicValueElement) return found;
|
|
356
|
+
this.#wait(id, found);
|
|
357
|
+
}
|
|
358
|
+
#unbind() {
|
|
359
|
+
for (const { unwatch } of this.#bindings.values()) unwatch();
|
|
360
|
+
this.#bindings.clear();
|
|
361
|
+
}
|
|
362
|
+
#wait(id, found) {
|
|
363
|
+
const tag = found?.localName;
|
|
364
|
+
const { ownerDocument } = this;
|
|
365
|
+
if (tag?.includes("-") && !customElements.get(tag)) {
|
|
366
|
+
this.#rebindOnce(tag, (rebind) => {
|
|
367
|
+
customElements.whenDefined(tag).then(rebind);
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (!found && ownerDocument.readyState === "loading") {
|
|
372
|
+
this.#rebindOnce(ownerDocument, (rebind) => {
|
|
373
|
+
ownerDocument.addEventListener("DOMContentLoaded", rebind, { once: true });
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
this.#warn(id);
|
|
378
|
+
}
|
|
379
|
+
#warn(id) {}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
//#endregion
|
|
383
|
+
export { SonicEnvelope };
|
|
@@ -1,61 +1,71 @@
|
|
|
1
|
+
import { toNumber } from "../lib/math.js";
|
|
1
2
|
import { template } from "../lib/render.js";
|
|
2
3
|
import { SonicElement } from "./sonic-element.js";
|
|
3
4
|
import { parseNumberList } from "../lib/number-list.js";
|
|
4
5
|
import { linearTaper } from "../lib/taper.js";
|
|
6
|
+
import { ballisticsRates, fall, rise, stepNeedle } from "../lib/ballistics.js";
|
|
5
7
|
|
|
6
8
|
//#region src/elements/meter.ts
|
|
7
9
|
const renderMeter = template(`
|
|
8
10
|
<div aria-hidden="true" class="sonic-meter">
|
|
9
11
|
<div class="sonic-meter-segments"></div>
|
|
10
|
-
<div class="sonic-meter-
|
|
12
|
+
<div class="sonic-meter-bar"></div>
|
|
11
13
|
<div class="sonic-meter-clip"></div>
|
|
12
14
|
</div>
|
|
13
15
|
`, HTMLDivElement);
|
|
14
|
-
const releaseDecibelsPerSecond = 20;
|
|
15
16
|
const holdMs = 1500;
|
|
17
|
+
const ballisticsModes = [
|
|
18
|
+
"peak",
|
|
19
|
+
"ppm-1",
|
|
20
|
+
"ppm-2",
|
|
21
|
+
"vu"
|
|
22
|
+
];
|
|
23
|
+
const settledDecibels = .01;
|
|
24
|
+
const settledAmplitude = 1e-4;
|
|
25
|
+
const settledPerSecond = .01;
|
|
16
26
|
function toDecibels(amplitude) {
|
|
17
27
|
return 20 * Math.log10(Math.abs(amplitude));
|
|
18
28
|
}
|
|
19
|
-
function
|
|
20
|
-
return
|
|
29
|
+
function toAmplitude(decibels) {
|
|
30
|
+
return 10 ** (decibels / 20);
|
|
31
|
+
}
|
|
32
|
+
function segmentsBelow(segments, zone) {
|
|
33
|
+
return `calc(${segments.map((threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`).join(" + ")})`;
|
|
21
34
|
}
|
|
22
35
|
var SonicMeter = class extends SonicElement {
|
|
23
36
|
static observedAttributes = [
|
|
37
|
+
"ballistics",
|
|
24
38
|
"disabled",
|
|
25
|
-
"lights",
|
|
26
39
|
"max",
|
|
27
40
|
"min",
|
|
28
41
|
"origin",
|
|
29
|
-
"
|
|
42
|
+
"segments",
|
|
43
|
+
"value"
|
|
30
44
|
];
|
|
45
|
+
get ballistics() {
|
|
46
|
+
const name = this.getAttribute("ballistics");
|
|
47
|
+
return ballisticsModes.find((mode) => mode === name) ?? "peak";
|
|
48
|
+
}
|
|
49
|
+
set ballistics(mode) {
|
|
50
|
+
this.reflect("ballistics", mode);
|
|
51
|
+
}
|
|
31
52
|
get level() {
|
|
32
53
|
return this.#level;
|
|
33
54
|
}
|
|
34
|
-
set level(
|
|
55
|
+
set level(level) {
|
|
56
|
+
const amplitude = toNumber(level);
|
|
35
57
|
if (!Number.isFinite(amplitude)) return;
|
|
36
|
-
const now = performance.now();
|
|
37
|
-
this.#level = amplitude;
|
|
38
|
-
if (this.scale === "linear") {
|
|
39
|
-
this.#render(now);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
58
|
const decibels = toDecibels(amplitude);
|
|
59
|
+
this.#level = amplitude;
|
|
43
60
|
this.#target = decibels;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.#peakAt = now;
|
|
48
|
-
}
|
|
61
|
+
if (this.#isValueShown) return;
|
|
62
|
+
const now = performance.now();
|
|
63
|
+
this.#peak = Math.max(this.#peak, decibels);
|
|
49
64
|
if (decibels >= 0) this.#clipAt = now;
|
|
65
|
+
this.#step(now, 0);
|
|
50
66
|
this.#render(now);
|
|
51
67
|
this.#schedule(now);
|
|
52
68
|
}
|
|
53
|
-
get lights() {
|
|
54
|
-
return parseNumberList(this.getAttribute("lights"));
|
|
55
|
-
}
|
|
56
|
-
set lights(thresholds) {
|
|
57
|
-
this.reflect("lights", thresholds?.join(" "));
|
|
58
|
-
}
|
|
59
69
|
get max() {
|
|
60
70
|
return this.numberAttribute("max", 0);
|
|
61
71
|
}
|
|
@@ -80,33 +90,52 @@ var SonicMeter = class extends SonicElement {
|
|
|
80
90
|
set origin(value) {
|
|
81
91
|
this.reflect("origin", value);
|
|
82
92
|
}
|
|
83
|
-
get
|
|
84
|
-
return this
|
|
93
|
+
get peak() {
|
|
94
|
+
return this.#peak;
|
|
95
|
+
}
|
|
96
|
+
get segments() {
|
|
97
|
+
return parseNumberList(this.getAttribute("segments"));
|
|
98
|
+
}
|
|
99
|
+
set segments(thresholds) {
|
|
100
|
+
this.reflect("segments", thresholds?.join(" "));
|
|
101
|
+
}
|
|
102
|
+
get value() {
|
|
103
|
+
return this.optionalNumberAttribute("value");
|
|
85
104
|
}
|
|
86
|
-
set
|
|
87
|
-
this.reflect("
|
|
105
|
+
set value(value) {
|
|
106
|
+
this.reflect("value", value);
|
|
88
107
|
}
|
|
89
108
|
#bar = -Infinity;
|
|
109
|
+
#carryMs = 0;
|
|
90
110
|
#clipAt = -Infinity;
|
|
91
111
|
#frame;
|
|
112
|
+
#isValueShown = false;
|
|
92
113
|
#lastFrame;
|
|
93
114
|
#level = 0;
|
|
94
|
-
#lights;
|
|
95
115
|
#meter = renderMeter();
|
|
96
116
|
#peak = -Infinity;
|
|
97
|
-
#
|
|
117
|
+
#peakHold = -Infinity;
|
|
118
|
+
#peakHoldAt = -Infinity;
|
|
119
|
+
#segments;
|
|
98
120
|
#target = -Infinity;
|
|
99
|
-
|
|
121
|
+
#velocity = 0;
|
|
122
|
+
attributeChangedCallback(name) {
|
|
100
123
|
const now = performance.now();
|
|
101
|
-
this.#
|
|
124
|
+
if (name === "ballistics") this.#velocity = 0;
|
|
125
|
+
else if (name === "value") this.#readValue(now);
|
|
126
|
+
this.#segments = this.segments;
|
|
127
|
+
if (name === "segments") this.#renderSegments();
|
|
102
128
|
this.#render(now);
|
|
103
129
|
this.#schedule(now);
|
|
104
130
|
}
|
|
131
|
+
resetPeak() {
|
|
132
|
+
this.#peak = -Infinity;
|
|
133
|
+
}
|
|
105
134
|
connect(signal) {
|
|
106
135
|
const now = performance.now();
|
|
107
|
-
this.upgradeProperties("
|
|
108
|
-
this.
|
|
109
|
-
this.#
|
|
136
|
+
this.upgradeProperties("ballistics", "max", "min", "orientation", "origin", "segments", "value", "level");
|
|
137
|
+
this.keepControl(this.#meter, signal);
|
|
138
|
+
this.#renderSegments();
|
|
110
139
|
this.#render(now);
|
|
111
140
|
this.#schedule(now);
|
|
112
141
|
this.checkStyles(this.#meter, "meter.css");
|
|
@@ -116,61 +145,124 @@ var SonicMeter = class extends SonicElement {
|
|
|
116
145
|
this.#lastFrame = void 0;
|
|
117
146
|
}, { once: true });
|
|
118
147
|
}
|
|
119
|
-
#fraction(level) {
|
|
120
|
-
const lights = this.#lights;
|
|
121
|
-
if (lights) return lights.filter((threshold) => threshold <= level).length / lights.length;
|
|
122
|
-
return linearTaper(this.min, this.max).position(level);
|
|
123
|
-
}
|
|
124
148
|
#isSettled(now) {
|
|
125
|
-
if (this
|
|
149
|
+
if (this.#isValueShown) return true;
|
|
150
|
+
if (now - this.#clipAt < holdMs) return false;
|
|
151
|
+
if (this.ballistics === "vu") return this.#bar === this.#target && this.#velocity === 0;
|
|
126
152
|
const floor = this.min;
|
|
127
|
-
return this.#bar <= Math.max(this.#target, floor) && this.#
|
|
153
|
+
return this.#bar >= this.#target && this.#bar <= Math.max(this.#target, floor) && this.#peakHold <= Math.max(this.#bar, floor);
|
|
154
|
+
}
|
|
155
|
+
#proportionOf(level) {
|
|
156
|
+
const segments = this.#segments;
|
|
157
|
+
if (segments) return segments.filter((threshold) => threshold <= level).length / segments.length;
|
|
158
|
+
return linearTaper(this.min, this.max).proportionOf(level);
|
|
159
|
+
}
|
|
160
|
+
#readValue(now) {
|
|
161
|
+
const value = this.value;
|
|
162
|
+
const isValueShown = value !== void 0;
|
|
163
|
+
if (isValueShown !== this.#isValueShown) {
|
|
164
|
+
this.#isValueShown = isValueShown;
|
|
165
|
+
this.resetPeak();
|
|
166
|
+
if (!isValueShown) this.#step(now, 0);
|
|
167
|
+
}
|
|
168
|
+
if (value !== void 0) this.#peak = Math.max(this.#peak, value);
|
|
128
169
|
}
|
|
129
170
|
#render(now) {
|
|
171
|
+
if (!this.isBound()) return;
|
|
130
172
|
const meter = this.#meter;
|
|
131
|
-
const origin = this.origin === void 0 ? 0 : this.#
|
|
173
|
+
const origin = this.origin === void 0 ? 0 : this.#proportionOf(this.origin);
|
|
132
174
|
meter.style.setProperty("--_sonic-meter-min", String(this.min));
|
|
133
175
|
meter.style.setProperty("--_sonic-meter-max", String(this.max));
|
|
134
176
|
meter.style.setProperty("--_sonic-meter-origin", String(origin));
|
|
135
|
-
if (this.disabled
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
177
|
+
if (this.disabled) {
|
|
178
|
+
this.#renderStill(origin);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const value = this.value;
|
|
182
|
+
if (value !== void 0) {
|
|
183
|
+
this.#renderStill(this.#proportionOf(value));
|
|
140
184
|
return;
|
|
141
185
|
}
|
|
142
|
-
|
|
143
|
-
meter.style.setProperty("--_sonic-meter-
|
|
186
|
+
const peakHold = this.ballistics === "vu" ? 0 : this.#proportionOf(this.#peakHold);
|
|
187
|
+
meter.style.setProperty("--_sonic-meter-bar", String(this.#proportionOf(this.#bar)));
|
|
188
|
+
meter.style.setProperty("--_sonic-meter-peak-hold", String(peakHold));
|
|
144
189
|
meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
|
|
145
190
|
}
|
|
146
|
-
#
|
|
191
|
+
#renderSegments() {
|
|
192
|
+
if (!this.isBound()) return;
|
|
147
193
|
const meter = this.#meter;
|
|
148
|
-
const
|
|
149
|
-
this
|
|
150
|
-
|
|
151
|
-
if (!lights) {
|
|
194
|
+
const segments = this.#segments;
|
|
195
|
+
this.toggleState("ladder", segments !== void 0);
|
|
196
|
+
if (!segments) {
|
|
152
197
|
for (const name of [
|
|
153
198
|
"count",
|
|
154
|
-
"hot-
|
|
155
|
-
"clip-
|
|
199
|
+
"hot-segments",
|
|
200
|
+
"clip-segments"
|
|
156
201
|
]) meter.style.removeProperty(`--_sonic-meter-${name}`);
|
|
157
202
|
return;
|
|
158
203
|
}
|
|
159
|
-
meter.style.setProperty("--_sonic-meter-count", String(
|
|
160
|
-
meter.style.setProperty("--_sonic-meter-hot-
|
|
161
|
-
meter.style.setProperty("--_sonic-meter-clip-
|
|
204
|
+
meter.style.setProperty("--_sonic-meter-count", String(segments.length));
|
|
205
|
+
meter.style.setProperty("--_sonic-meter-hot-segments", segmentsBelow(segments, "hot"));
|
|
206
|
+
meter.style.setProperty("--_sonic-meter-clip-segments", segmentsBelow(segments, "clip"));
|
|
207
|
+
}
|
|
208
|
+
#renderStill(bar) {
|
|
209
|
+
const { style } = this.#meter;
|
|
210
|
+
style.setProperty("--_sonic-meter-bar", String(bar));
|
|
211
|
+
style.setProperty("--_sonic-meter-peak-hold", "0");
|
|
212
|
+
style.setProperty("--_sonic-meter-clipped", "0");
|
|
213
|
+
}
|
|
214
|
+
#risen(timeConstantMs, elapsedMs) {
|
|
215
|
+
const target = toAmplitude(this.#target);
|
|
216
|
+
const risen = toDecibels(rise(toAmplitude(this.#bar), {
|
|
217
|
+
target,
|
|
218
|
+
timeConstantMs
|
|
219
|
+
}, elapsedMs));
|
|
220
|
+
return this.#target - risen < settledDecibels ? this.#target : risen;
|
|
162
221
|
}
|
|
163
222
|
#schedule(now) {
|
|
164
223
|
if (this.#frame !== void 0 || !this.isConnected || this.#isSettled(now)) return;
|
|
165
224
|
if (this.#lastFrame === void 0) this.#lastFrame = now;
|
|
166
225
|
this.#frame = requestAnimationFrame(this.#tick);
|
|
167
226
|
}
|
|
227
|
+
#step(now, elapsedMs) {
|
|
228
|
+
const mode = this.ballistics;
|
|
229
|
+
if (mode === "vu") {
|
|
230
|
+
this.#stepNeedle(elapsedMs);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];
|
|
234
|
+
const floor = Math.max(this.#target, this.min);
|
|
235
|
+
this.#bar = this.#target > this.#bar ? this.#risen(riseTimeConstantMs, elapsedMs) : fall(this.#bar, {
|
|
236
|
+
fallDecibelsPerSecond,
|
|
237
|
+
floor
|
|
238
|
+
}, elapsedMs);
|
|
239
|
+
if (this.#bar >= this.#peakHold) {
|
|
240
|
+
this.#peakHold = this.#bar;
|
|
241
|
+
this.#peakHoldAt = now;
|
|
242
|
+
}
|
|
243
|
+
const pastHoldMs = now - this.#peakHoldAt - holdMs;
|
|
244
|
+
if (pastHoldMs >= 0) this.#peakHold = fall(this.#peakHold, {
|
|
245
|
+
fallDecibelsPerSecond,
|
|
246
|
+
floor: this.#bar
|
|
247
|
+
}, Math.min(elapsedMs, pastHoldMs));
|
|
248
|
+
}
|
|
249
|
+
#stepNeedle(elapsedMs) {
|
|
250
|
+
const target = toAmplitude(this.#target);
|
|
251
|
+
const needle = stepNeedle({
|
|
252
|
+
carryMs: this.#carryMs,
|
|
253
|
+
position: toAmplitude(this.#bar),
|
|
254
|
+
velocity: this.#velocity
|
|
255
|
+
}, target, elapsedMs);
|
|
256
|
+
const isLanded = Math.abs(needle.position - target) < settledAmplitude && Math.abs(needle.velocity) < settledPerSecond;
|
|
257
|
+
this.#carryMs = needle.carryMs;
|
|
258
|
+
this.#velocity = isLanded ? 0 : needle.velocity;
|
|
259
|
+
this.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));
|
|
260
|
+
}
|
|
168
261
|
#tick = (time) => {
|
|
169
|
-
const
|
|
262
|
+
const elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));
|
|
170
263
|
this.#frame = void 0;
|
|
171
264
|
this.#lastFrame = time;
|
|
172
|
-
this.#
|
|
173
|
-
if (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);
|
|
265
|
+
this.#step(time, elapsedMs);
|
|
174
266
|
this.#render(time);
|
|
175
267
|
if (this.#isSettled(time)) {
|
|
176
268
|
this.#lastFrame = void 0;
|