@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
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import { SonicElement } from '#elements/sonic-element.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-level\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst releaseDecibelsPerSecond = 20;\nconst holdMs = 1500;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction lightsBelow(lights: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = lights.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\n// On the default `db` scale, `level` is a linear amplitude shown in dBFS; on `linear`, it is in the units of `min` and `max`\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'disabled',\n\t\t'lights',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'scale',\n\t];\n\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(amplitude: number) {\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#level = amplitude;\n\t\tif (this.scale === 'linear') {\n\t\t\tthis.#render(now);\n\t\t\treturn;\n\t\t}\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#target = decibels;\n\t\tthis.#bar = Math.max(this.#bar, decibels);\n\t\tif (decibels >= this.#peak) {\n\t\t\tthis.#peak = decibels;\n\t\t\tthis.#peakAt = now;\n\t\t}\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget lights(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('lights'));\n\t}\n\n\tset lights(thresholds: Array<number> | undefined) {\n\t\tthis.reflect('lights', thresholds?.join(' '));\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute, as on a native element\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget scale(): 'db' | 'linear' {\n\t\treturn this.getAttribute('scale') === 'linear' ? 'linear' : 'db';\n\t}\n\n\tset scale(scale: 'db' | 'linear' | undefined) {\n\t\tthis.reflect('scale', scale);\n\t}\n\n\t#bar = -Infinity;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\t// Parsed once per attribute change rather than per frame\n\t#lights: Array<number> | undefined;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakAt = -Infinity;\n\n\t#target = -Infinity;\n\n\tattributeChangedCallback(): void {\n\t\tconst now = performance.now();\n\n\t\tthis.#renderLights();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties('lights', 'max', 'min', 'orientation', 'origin', 'scale', 'level');\n\t\tthis.appendOnce(this.#meter);\n\t\tthis.#renderLights();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tthis.checkStyles(this.#meter, 'meter.css');\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#fraction(level: number): number {\n\t\tconst lights = this.#lights;\n\t\tif (lights) return lights.filter((threshold) => threshold <= level).length / lights.length;\n\n\t\treturn linearTaper(this.min, this.max).position(level);\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.scale === 'linear') return true;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peak <= Math.max(this.#bar, floor) &&\n\t\t\tnow - this.#clipAt >= holdMs\n\t\t);\n\t}\n\n\t#render(now: number): void {\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#fraction(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\t// Disabled puts the light out, as on the other controls; the ballistics keep running so it lights up mid-fall\n\t\t// The consumer smooths a linear level, so it has no fall, hold or clip\n\t\tif (this.disabled || this.scale === 'linear') {\n\t\t\tconst level = this.disabled ? origin : this.#fraction(this.#level);\n\n\t\t\tmeter.style.setProperty('--_sonic-meter-level', String(level));\n\t\t\tmeter.style.setProperty('--_sonic-meter-peak', '0');\n\t\t\tmeter.style.setProperty('--_sonic-meter-clipped', '0');\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-level', String(this.#fraction(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak', String(this.#fraction(this.#peak)));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderLights(): void {\n\t\tconst meter = this.#meter;\n\t\tconst lights = this.lights;\n\n\t\tthis.#lights = lights;\n\t\tthis.toggleState('ladder', lights !== undefined);\n\t\tif (!lights) {\n\t\t\tfor (const name of ['count', 'hot-lights', 'clip-lights']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(lights.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-lights', lightsBelow(lights, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-lights', lightsBelow(lights, 'clip'));\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t// Timed by the frame rather than counted, so a 120Hz display falls at the same speed\n\treadonly #tick = (time: number): void => {\n\t\tconst fall = (releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time))) / 1000;\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#bar = Math.max(this.#target, this.min, this.#bar - fall);\n\t\tif (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import { SonicElement } from '#elements/sonic-element.ts';\nimport { ballisticsRates, fall, rise, stepNeedle } from '#lib/ballistics.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-bar\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst holdMs = 1500;\n\nconst ballisticsModes = ['peak', 'ppm-1', 'ppm-2', 'vu'] as const;\n\nconst settledDecibels = 0.01;\nconst settledAmplitude = 1e-4;\nconst settledPerSecond = 1e-2;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\nfunction toAmplitude(decibels: number): number {\n\treturn 10 ** (decibels / 20);\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction segmentsBelow(segments: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = segments.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'ballistics',\n\t\t'disabled',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'segments',\n\t\t'value',\n\t];\n\n\tget ballistics(): (typeof ballisticsModes)[number] {\n\t\tconst name = this.getAttribute('ballistics');\n\n\t\treturn ballisticsModes.find((mode) => mode === name) ?? 'peak';\n\t}\n\n\tset ballistics(mode: (typeof ballisticsModes)[number] | undefined) {\n\t\tthis.reflect('ballistics', mode);\n\t}\n\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(level: number) {\n\t\tconst amplitude = toNumber(level);\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tif (this.#isValueShown) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#peak = Math.max(this.#peak, decibels);\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#step(now, 0);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget peak(): number {\n\t\treturn this.#peak;\n\t}\n\n\tget segments(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('segments'));\n\t}\n\n\tset segments(thresholds: Array<number> | undefined) {\n\t\tthis.reflect('segments', thresholds?.join(' '));\n\t}\n\n\tget value(): number | undefined {\n\t\treturn this.optionalNumberAttribute('value');\n\t}\n\n\tset value(value: number | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\t#bar = -Infinity;\n\n\t#carryMs = 0;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#isValueShown = false;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakHold = -Infinity;\n\n\t#peakHoldAt = -Infinity;\n\n\t#segments: Array<number> | undefined;\n\n\t#target = -Infinity;\n\n\t#velocity = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst now = performance.now();\n\n\t\tif (name === 'ballistics') this.#velocity = 0;\n\t\telse if (name === 'value') this.#readValue(now);\n\t\tthis.#segments = this.segments;\n\t\tif (name === 'segments') this.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = -Infinity;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties(\n\t\t\t'ballistics',\n\t\t\t'max',\n\t\t\t'min',\n\t\t\t'orientation',\n\t\t\t'origin',\n\t\t\t'segments',\n\t\t\t'value',\n\t\t\t'level',\n\t\t);\n\t\tthis.keepControl(this.#meter, signal);\n\t\tthis.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tthis.checkStyles(this.#meter, 'meter.css');\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.#isValueShown) return true;\n\t\tif (now - this.#clipAt < holdMs) return false;\n\t\tif (this.ballistics === 'vu') return this.#bar === this.#target && this.#velocity === 0;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar >= this.#target &&\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peakHold <= Math.max(this.#bar, floor)\n\t\t);\n\t}\n\n\t#proportionOf(level: number): number {\n\t\tconst segments = this.#segments;\n\t\tif (segments) {\n\t\t\treturn segments.filter((threshold) => threshold <= level).length / segments.length;\n\t\t}\n\n\t\treturn linearTaper(this.min, this.max).proportionOf(level);\n\t}\n\n\t#readValue(now: number): void {\n\t\tconst value = this.value;\n\t\tconst isValueShown = value !== undefined;\n\n\t\tif (isValueShown !== this.#isValueShown) {\n\t\t\tthis.#isValueShown = isValueShown;\n\t\t\tthis.resetPeak();\n\t\t\tif (!isValueShown) this.#step(now, 0);\n\t\t}\n\t\tif (value !== undefined) this.#peak = Math.max(this.#peak, value);\n\t}\n\n\t#render(now: number): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#proportionOf(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\tif (this.disabled) {\n\t\t\tthis.#renderStill(origin);\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = this.value;\n\n\t\tif (value !== undefined) {\n\t\t\tthis.#renderStill(this.#proportionOf(value));\n\t\t\treturn;\n\t\t}\n\n\t\tconst peakHold = this.ballistics === 'vu' ? 0 : this.#proportionOf(this.#peakHold);\n\n\t\tmeter.style.setProperty('--_sonic-meter-bar', String(this.#proportionOf(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak-hold', String(peakHold));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderSegments(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\t\tconst segments = this.#segments;\n\n\t\tthis.toggleState('ladder', segments !== undefined);\n\t\tif (!segments) {\n\t\t\tfor (const name of ['count', 'hot-segments', 'clip-segments']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(segments.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-segments', segmentsBelow(segments, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-segments', segmentsBelow(segments, 'clip'));\n\t}\n\n\t#renderStill(bar: number): void {\n\t\tconst { style } = this.#meter;\n\n\t\tstyle.setProperty('--_sonic-meter-bar', String(bar));\n\t\tstyle.setProperty('--_sonic-meter-peak-hold', '0');\n\t\tstyle.setProperty('--_sonic-meter-clipped', '0');\n\t}\n\n\t#risen(timeConstantMs: number, elapsedMs: number): number {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst risen = toDecibels(rise(toAmplitude(this.#bar), { target, timeConstantMs }, elapsedMs));\n\n\t\treturn this.#target - risen < settledDecibels ? this.#target : risen;\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t#step(now: number, elapsedMs: number): void {\n\t\tconst mode = this.ballistics;\n\n\t\tif (mode === 'vu') {\n\t\t\tthis.#stepNeedle(elapsedMs);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];\n\t\tconst floor = Math.max(this.#target, this.min);\n\n\t\tthis.#bar =\n\t\t\tthis.#target > this.#bar\n\t\t\t\t? this.#risen(riseTimeConstantMs, elapsedMs)\n\t\t\t\t: fall(this.#bar, { fallDecibelsPerSecond, floor }, elapsedMs);\n\t\tif (this.#bar >= this.#peakHold) {\n\t\t\tthis.#peakHold = this.#bar;\n\t\t\tthis.#peakHoldAt = now;\n\t\t}\n\n\t\tconst pastHoldMs = now - this.#peakHoldAt - holdMs;\n\n\t\tif (pastHoldMs >= 0) {\n\t\t\tthis.#peakHold = fall(\n\t\t\t\tthis.#peakHold,\n\t\t\t\t{ fallDecibelsPerSecond, floor: this.#bar },\n\t\t\t\tMath.min(elapsedMs, pastHoldMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t#stepNeedle(elapsedMs: number): void {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst needle = stepNeedle(\n\t\t\t{ carryMs: this.#carryMs, position: toAmplitude(this.#bar), velocity: this.#velocity },\n\t\t\ttarget,\n\t\t\telapsedMs,\n\t\t);\n\t\tconst isLanded =\n\t\t\tMath.abs(needle.position - target) < settledAmplitude &&\n\t\t\tMath.abs(needle.velocity) < settledPerSecond;\n\n\t\tthis.#carryMs = needle.carryMs;\n\t\tthis.#velocity = isLanded ? 0 : needle.velocity;\n\t\tthis.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));\n\t}\n\n\treadonly #tick = (time: number): void => {\n\t\tconst elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#step(time, elapsedMs);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SonicValueElement } from "./value-element.js";
|
|
2
2
|
//#region src/elements/number.d.ts
|
|
3
3
|
declare global {
|
|
4
4
|
interface HTMLElementTagNameMap {
|
|
5
5
|
'sonic-number': SonicNumber;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
export declare class SonicNumber extends
|
|
8
|
+
export declare class SonicNumber extends SonicValueElement {
|
|
9
9
|
#private;
|
|
10
|
+
get press(): 'none' | 'step';
|
|
11
|
+
set press(gesture: 'none' | 'step' | undefined);
|
|
12
|
+
connectedCallback(): void;
|
|
10
13
|
protected connect(signal: AbortSignal): void;
|
|
11
14
|
protected controlRole(): 'spinbutton';
|
|
15
|
+
protected draw(): void;
|
|
12
16
|
protected focusTarget(): HTMLElement;
|
|
13
|
-
protected
|
|
17
|
+
protected tapTarget(): number | undefined;
|
|
14
18
|
}
|
|
15
19
|
//#endregion
|
|
16
20
|
//# sourceMappingURL=number.d.ts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
-
import {
|
|
3
|
-
import { SonicRangeElement } from "./range-element.js";
|
|
2
|
+
import { SonicValueElement } from "./value-element.js";
|
|
4
3
|
|
|
5
4
|
//#region src/elements/number.ts
|
|
6
5
|
const renderNumber = template(`
|
|
@@ -16,29 +15,43 @@ const renderNumber = template(`
|
|
|
16
15
|
/>
|
|
17
16
|
</div>
|
|
18
17
|
`, HTMLDivElement);
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
var SonicNumber = class extends SonicValueElement {
|
|
19
|
+
get press() {
|
|
20
|
+
return this.getAttribute("press") === "step" ? "step" : "none";
|
|
21
|
+
}
|
|
22
|
+
set press(gesture) {
|
|
23
|
+
this.reflect("press", gesture);
|
|
24
|
+
}
|
|
21
25
|
#number = renderNumber();
|
|
22
26
|
#digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
this.upgradeProperties("press");
|
|
29
|
+
super.connectedCallback();
|
|
30
|
+
}
|
|
23
31
|
connect(signal) {
|
|
24
32
|
const number = this.#number;
|
|
25
|
-
this.
|
|
33
|
+
this.keepControl(number, signal);
|
|
26
34
|
this.render();
|
|
27
35
|
this.checkStyles(number, "number.css");
|
|
28
36
|
this.bindGestures(number, signal, () => ({
|
|
29
37
|
position: (event) => -event.clientY,
|
|
30
|
-
travelPx:
|
|
38
|
+
travelPx: this.travelPx(number, "--_sonic-number-travel")
|
|
31
39
|
}));
|
|
32
40
|
}
|
|
33
41
|
controlRole() {
|
|
34
42
|
return "spinbutton";
|
|
35
43
|
}
|
|
44
|
+
draw() {
|
|
45
|
+
this.#digits.textContent = this.valueText;
|
|
46
|
+
}
|
|
36
47
|
focusTarget() {
|
|
37
48
|
return this.#number;
|
|
38
49
|
}
|
|
39
|
-
|
|
40
|
-
this
|
|
41
|
-
this.
|
|
50
|
+
tapTarget() {
|
|
51
|
+
if (this.press !== "step") return void 0;
|
|
52
|
+
const mapping = this.mapping();
|
|
53
|
+
const next = mapping.keyTarget("ArrowUp", this.value);
|
|
54
|
+
return next === void 0 || next === this.value ? mapping.bounds[0] : next;
|
|
42
55
|
}
|
|
43
56
|
};
|
|
44
57
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tthis.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AASA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EACZ,KAAK,YAAY,QAAQ,YAAY;EACrC,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SonicFormElement } from "./form-element.js";
|
|
2
|
+
//#region src/elements/radio-group.d.ts
|
|
3
|
+
export declare abstract class SonicRadioGroupElement extends SonicFormElement {
|
|
4
|
+
#private;
|
|
5
|
+
static readonly observedAttributes: string[];
|
|
6
|
+
get value(): string;
|
|
7
|
+
set value(next: string);
|
|
8
|
+
protected abstract readonly group: HTMLElement;
|
|
9
|
+
attributeChangedCallback(name: string): void;
|
|
10
|
+
protected chooseByClick(option: HTMLButtonElement): void;
|
|
11
|
+
protected chooseByKey(option: HTMLButtonElement, _event: KeyboardEvent): void;
|
|
12
|
+
protected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean;
|
|
13
|
+
protected connect(signal: AbortSignal): void;
|
|
14
|
+
protected focusTarget(): HTMLElement | undefined;
|
|
15
|
+
protected isWrapping(): boolean;
|
|
16
|
+
protected optionOf(target: EventTarget | null): HTMLButtonElement | undefined;
|
|
17
|
+
protected options(): Array<HTMLButtonElement>;
|
|
18
|
+
protected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined;
|
|
19
|
+
protected render(): void;
|
|
20
|
+
protected abstract renderOption(): HTMLButtonElement;
|
|
21
|
+
protected restoreState(state: string): void;
|
|
22
|
+
protected select(option: HTMLButtonElement): void;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//# sourceMappingURL=radio-group.d.ts.map
|