@xsynaptic/sonic-ui 0.4.0 → 0.5.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 +2 -21
- package/dist/default/dat.js +50 -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 +367 -0
- package/dist/default/elements/meter.js +149 -61
- package/dist/default/elements/number.js +21 -8
- package/dist/default/elements/radio-group.js +185 -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 +24 -13
- package/dist/default/elements/split.js +249 -0
- package/dist/default/elements/switch.js +279 -0
- package/dist/default/elements/value-element.js +691 -0
- package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
- package/dist/default/elements/wave-element.js +77 -0
- package/dist/default/elements/waveform.js +305 -0
- package/dist/default/elements/wavestrip.js +182 -0
- package/dist/default/elements/xy.js +473 -0
- package/dist/default/index.js +10 -2
- package/dist/default/lib/adsr-shape.js +108 -0
- package/dist/default/lib/ballistics.js +55 -0
- package/dist/default/lib/canvas-surface.js +192 -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 +62 -0
- package/dist/default/lib/hold.js +38 -0
- package/dist/default/lib/marker-band.js +36 -0
- package/dist/default/lib/marker-rider.js +114 -0
- package/dist/default/lib/math.js +12 -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 +43 -0
- package/dist/default/lib/render.js +20 -5
- package/dist/default/lib/resample-peaks.js +36 -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/dev/dat.d.ts +19 -0
- package/dist/dev/dat.js +51 -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 +372 -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 +10 -5
- package/dist/dev/elements/meter.js +149 -61
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +7 -3
- package/dist/dev/elements/number.js +21 -8
- 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 +186 -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 +4 -3
- package/dist/dev/elements/sonic-element.js +51 -22
- 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 +280 -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 +692 -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 +36 -0
- package/dist/dev/elements/wave-element.js +78 -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 +306 -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 +183 -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 +474 -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 +109 -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 +25 -0
- package/dist/dev/lib/canvas-surface.js +193 -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 +63 -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 +37 -0
- package/dist/dev/lib/marker-band.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +115 -0
- package/dist/dev/lib/marker-rider.js.map +1 -0
- package/dist/dev/lib/math.js +12 -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 +44 -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 +37 -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/skins/ivory.css +1 -2
- package/dist/styles/button.css +212 -0
- package/dist/styles/controls.css +8 -1
- package/dist/styles/dial.css +88 -42
- package/dist/styles/envelope.css +159 -0
- package/dist/styles/led.css +36 -5
- package/dist/styles/material.css +277 -76
- package/dist/styles/meter.css +33 -48
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +59 -0
- package/dist/styles/ring.css +74 -0
- package/dist/styles/segmented.css +21 -48
- package/dist/styles/slider.css +175 -39
- package/dist/styles/switch.css +347 -0
- package/dist/styles/waveform.css +173 -0
- package/dist/styles/wavestrip.css +215 -0
- package/dist/styles/xy.css +204 -0
- package/package.json +14 -2
- 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,77 @@
|
|
|
1
|
+
import { SonicValueElement } from "./value-element.js";
|
|
2
|
+
import { bindSurface } from "../lib/canvas-surface.js";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/wave-element.ts
|
|
5
|
+
var SonicWaveElement = class extends SonicValueElement {
|
|
6
|
+
get markers() {
|
|
7
|
+
return this.#markers.map((marker) => ({ ...marker }));
|
|
8
|
+
}
|
|
9
|
+
set markers(list) {
|
|
10
|
+
this.#markers = [...list ?? []].filter((marker) => Number.isFinite(marker.start)).map((marker) => ({ ...marker })).toSorted((first, second) => first.start - second.start);
|
|
11
|
+
this.renderMarkers();
|
|
12
|
+
}
|
|
13
|
+
#heldAt;
|
|
14
|
+
#markers = [];
|
|
15
|
+
#surface;
|
|
16
|
+
connectedCallback() {
|
|
17
|
+
this.upgradeProperties("markers");
|
|
18
|
+
super.connectedCallback();
|
|
19
|
+
}
|
|
20
|
+
repaint() {
|
|
21
|
+
this.#surface?.rebuild();
|
|
22
|
+
}
|
|
23
|
+
connect(signal) {
|
|
24
|
+
const control = this.control;
|
|
25
|
+
this.appendOnce(control);
|
|
26
|
+
this.renderEmpty();
|
|
27
|
+
this.#surface = bindSurface({
|
|
28
|
+
canvas: this.canvas,
|
|
29
|
+
colours: this.colours,
|
|
30
|
+
lengths: this.lengths,
|
|
31
|
+
paint: (context, frame) => {
|
|
32
|
+
this.paint(context, frame);
|
|
33
|
+
},
|
|
34
|
+
resize: () => {
|
|
35
|
+
this.renderMarkers();
|
|
36
|
+
},
|
|
37
|
+
signal
|
|
38
|
+
});
|
|
39
|
+
this.render();
|
|
40
|
+
this.checkStyles(control, this.sheet);
|
|
41
|
+
}
|
|
42
|
+
focusTarget() {
|
|
43
|
+
return this.control;
|
|
44
|
+
}
|
|
45
|
+
heldWrite(next) {
|
|
46
|
+
this.#heldAt = next;
|
|
47
|
+
this.render();
|
|
48
|
+
}
|
|
49
|
+
holdChanged() {
|
|
50
|
+
if (this.heldFrom() === void 0) this.#heldAt = void 0;
|
|
51
|
+
this.render();
|
|
52
|
+
}
|
|
53
|
+
markerProportions() {
|
|
54
|
+
const mapping = this.mapping();
|
|
55
|
+
return this.#markers.map(({ end, start }) => {
|
|
56
|
+
const at = mapping.proportionOf(start);
|
|
57
|
+
const to = end === void 0 ? at : mapping.proportionOf(end);
|
|
58
|
+
return [Math.min(at, to), Math.max(at, to)];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
playback() {
|
|
62
|
+
const from = this.heldFrom();
|
|
63
|
+
return from === void 0 ? this.value : this.#heldAt ?? from;
|
|
64
|
+
}
|
|
65
|
+
proportionsChanged() {
|
|
66
|
+
this.renderMarkers();
|
|
67
|
+
}
|
|
68
|
+
renderEmpty() {
|
|
69
|
+
this.toggleState("empty", this.isEmpty());
|
|
70
|
+
}
|
|
71
|
+
surface() {
|
|
72
|
+
return this.#surface;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { SonicWaveElement };
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { clamp } from "../lib/math.js";
|
|
2
|
+
import { requireChild, template } from "../lib/render.js";
|
|
3
|
+
import { readRegions } from "../lib/time-regions.js";
|
|
4
|
+
import { SonicWaveElement } from "./wave-element.js";
|
|
5
|
+
import { frameTimeline } from "../lib/frame-timeline.js";
|
|
6
|
+
import { createLabelRider } from "../lib/marker-rider.js";
|
|
7
|
+
import { createTrackingClock } from "../lib/tracking-clock.js";
|
|
8
|
+
import { waveformBuckets } from "../lib/waveform-buckets.js";
|
|
9
|
+
|
|
10
|
+
//#region src/elements/waveform.ts
|
|
11
|
+
const colours = {
|
|
12
|
+
grid: "--_sonic-waveform-grid",
|
|
13
|
+
hatch: "--_sonic-waveform-hatch",
|
|
14
|
+
marker: "--_sonic-waveform-marker",
|
|
15
|
+
wave: "--_sonic-waveform-wave",
|
|
16
|
+
waveEdge: "--_sonic-waveform-wave-edge"
|
|
17
|
+
};
|
|
18
|
+
const defaultZoom = 70;
|
|
19
|
+
const amplitudeMargin = .94;
|
|
20
|
+
const hatchPitchPx = 8;
|
|
21
|
+
const placeholderWavelengthPx = 48;
|
|
22
|
+
const placeholderAmplitude = .2;
|
|
23
|
+
const renderWaveform = template(`
|
|
24
|
+
<div class="sonic-waveform" role="slider" tabindex="0">
|
|
25
|
+
<canvas class="sonic-waveform-canvas" aria-hidden="true"></canvas>
|
|
26
|
+
<div class="sonic-waveform-ghost" aria-hidden="true" hidden></div>
|
|
27
|
+
<div class="sonic-waveform-playhead" aria-hidden="true"></div>
|
|
28
|
+
<div class="sonic-waveform-label" aria-hidden="true" hidden></div>
|
|
29
|
+
<div class="sonic-waveform-label" aria-hidden="true" hidden></div>
|
|
30
|
+
<div class="sonic-waveform-readout" popover="manual">
|
|
31
|
+
<span></span>
|
|
32
|
+
<input
|
|
33
|
+
autocomplete="off"
|
|
34
|
+
class="sonic-waveform-entry"
|
|
35
|
+
enterkeyhint="done"
|
|
36
|
+
hidden
|
|
37
|
+
spellcheck="false"
|
|
38
|
+
type="text"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
`, HTMLDivElement);
|
|
43
|
+
function placeLine(line, at) {
|
|
44
|
+
const isHidden = at === void 0 || at < 0 || at > 1;
|
|
45
|
+
if (line.hidden !== isHidden) line.hidden = isHidden;
|
|
46
|
+
if (isHidden) return;
|
|
47
|
+
const translate = `${String(at * 100)}cqi`;
|
|
48
|
+
if (line.style.getPropertyValue("translate") !== translate) line.style.setProperty("translate", translate);
|
|
49
|
+
}
|
|
50
|
+
function paintHatch(context, scene, [from, to]) {
|
|
51
|
+
if (to <= from) return;
|
|
52
|
+
const { dpr, height } = scene;
|
|
53
|
+
const pitch = Math.max(2, Math.round(hatchPitchPx * dpr));
|
|
54
|
+
const offset = scene.startSeconds * scene.pixelsPerSecond;
|
|
55
|
+
context.save();
|
|
56
|
+
context.beginPath();
|
|
57
|
+
context.rect(from, 0, to - from, height);
|
|
58
|
+
context.clip();
|
|
59
|
+
context.beginPath();
|
|
60
|
+
for (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {
|
|
61
|
+
context.moveTo(x, height);
|
|
62
|
+
context.lineTo(x + height, 0);
|
|
63
|
+
}
|
|
64
|
+
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
65
|
+
context.strokeStyle = scene.colours.hatch;
|
|
66
|
+
context.stroke();
|
|
67
|
+
context.restore();
|
|
68
|
+
}
|
|
69
|
+
function paintPlaceholders(context, scene) {
|
|
70
|
+
if (scene.pending.length === 0) return;
|
|
71
|
+
const { dpr, height, pixelsPerSecond, startSeconds } = scene;
|
|
72
|
+
const wavelength = placeholderWavelengthPx * dpr;
|
|
73
|
+
const centre = height / 2;
|
|
74
|
+
const origin = startSeconds * pixelsPerSecond;
|
|
75
|
+
const waveY = (x) => centre - Math.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) * centre * placeholderAmplitude;
|
|
76
|
+
context.beginPath();
|
|
77
|
+
for (const [from, to] of scene.pending) {
|
|
78
|
+
const opening = (from - startSeconds) * pixelsPerSecond;
|
|
79
|
+
const closing = (to - startSeconds) * pixelsPerSecond;
|
|
80
|
+
context.moveTo(opening, waveY(opening));
|
|
81
|
+
for (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));
|
|
82
|
+
context.lineTo(closing, waveY(closing));
|
|
83
|
+
}
|
|
84
|
+
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
85
|
+
context.strokeStyle = scene.colours.hatch;
|
|
86
|
+
context.stroke();
|
|
87
|
+
}
|
|
88
|
+
function waveFill(context, scene) {
|
|
89
|
+
const { wave, waveEdge } = scene.colours;
|
|
90
|
+
const gradient = context.createLinearGradient(0, 0, 0, scene.height);
|
|
91
|
+
gradient.addColorStop(0, waveEdge);
|
|
92
|
+
gradient.addColorStop(.5, wave);
|
|
93
|
+
gradient.addColorStop(1, waveEdge);
|
|
94
|
+
return gradient;
|
|
95
|
+
}
|
|
96
|
+
function paintWave(context, scene, peaks) {
|
|
97
|
+
const buckets = waveformBuckets(peaks, scene);
|
|
98
|
+
const [first] = buckets;
|
|
99
|
+
if (!first) return;
|
|
100
|
+
const centre = scene.height / 2;
|
|
101
|
+
const amplitudePx = centre * amplitudeMargin;
|
|
102
|
+
context.fillStyle = waveFill(context, scene);
|
|
103
|
+
context.beginPath();
|
|
104
|
+
context.moveTo(first.x, centre);
|
|
105
|
+
for (const bucket of buckets) context.lineTo(bucket.x, centre - bucket.high * amplitudePx);
|
|
106
|
+
for (const bucket of buckets.toReversed()) context.lineTo(bucket.x, centre - bucket.low * amplitudePx);
|
|
107
|
+
context.closePath();
|
|
108
|
+
context.fill();
|
|
109
|
+
}
|
|
110
|
+
var SonicWaveform = class extends SonicWaveElement {
|
|
111
|
+
static observedAttributes = [
|
|
112
|
+
...SonicWaveElement.observedAttributes,
|
|
113
|
+
"playing",
|
|
114
|
+
"playback-rate",
|
|
115
|
+
"zoom"
|
|
116
|
+
];
|
|
117
|
+
get peaks() {
|
|
118
|
+
return this.#peaks;
|
|
119
|
+
}
|
|
120
|
+
set peaks(peaks) {
|
|
121
|
+
this.#peaks = peaks;
|
|
122
|
+
this.renderEmpty();
|
|
123
|
+
this.surface()?.invalidate();
|
|
124
|
+
}
|
|
125
|
+
get pending() {
|
|
126
|
+
return this.#pending.map(([start, end]) => [start, end]);
|
|
127
|
+
}
|
|
128
|
+
set pending(regions) {
|
|
129
|
+
this.#pending = regions ? readRegions(regions) : [];
|
|
130
|
+
this.surface()?.invalidate();
|
|
131
|
+
}
|
|
132
|
+
get playbackRate() {
|
|
133
|
+
const playbackRate = this.numberAttribute("playback-rate", 1);
|
|
134
|
+
return playbackRate > 0 ? playbackRate : 1;
|
|
135
|
+
}
|
|
136
|
+
set playbackRate(value) {
|
|
137
|
+
this.reflect("playback-rate", value);
|
|
138
|
+
}
|
|
139
|
+
get playing() {
|
|
140
|
+
return this.hasAttribute("playing");
|
|
141
|
+
}
|
|
142
|
+
set playing(isPlaying) {
|
|
143
|
+
this.reflect("playing", isPlaying);
|
|
144
|
+
}
|
|
145
|
+
get readTime() {
|
|
146
|
+
return this.#readTime;
|
|
147
|
+
}
|
|
148
|
+
set readTime(read) {
|
|
149
|
+
this.#readTime = read;
|
|
150
|
+
this.render();
|
|
151
|
+
}
|
|
152
|
+
get requestPeaks() {
|
|
153
|
+
return this.#requestPeaks;
|
|
154
|
+
}
|
|
155
|
+
set requestPeaks(request) {
|
|
156
|
+
this.#requestPeaks = request;
|
|
157
|
+
this.surface()?.invalidate();
|
|
158
|
+
}
|
|
159
|
+
get zoom() {
|
|
160
|
+
const zoom = this.numberAttribute("zoom", defaultZoom);
|
|
161
|
+
return zoom > 0 ? zoom : defaultZoom;
|
|
162
|
+
}
|
|
163
|
+
set zoom(value) {
|
|
164
|
+
this.reflect("zoom", value);
|
|
165
|
+
}
|
|
166
|
+
control = renderWaveform();
|
|
167
|
+
canvas = requireChild(this.control, ".sonic-waveform-canvas", HTMLCanvasElement);
|
|
168
|
+
colours = colours;
|
|
169
|
+
lengths = {};
|
|
170
|
+
sheet = "waveform.css";
|
|
171
|
+
#clock = createTrackingClock();
|
|
172
|
+
#drawn;
|
|
173
|
+
#ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
|
|
174
|
+
#grabbed;
|
|
175
|
+
#paintedKey = "";
|
|
176
|
+
#peaks;
|
|
177
|
+
#pending = [];
|
|
178
|
+
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
179
|
+
#readTime;
|
|
180
|
+
#requestPeaks;
|
|
181
|
+
#rider = createLabelRider(this.control, {
|
|
182
|
+
className: "sonic-waveform-label",
|
|
183
|
+
insetProperty: "--_sonic-waveform-label-inset"
|
|
184
|
+
});
|
|
185
|
+
connectedCallback() {
|
|
186
|
+
this.upgradeProperties("peaks", "pending", "playing", "playbackRate", "readTime", "requestPeaks", "zoom");
|
|
187
|
+
super.connectedCallback();
|
|
188
|
+
}
|
|
189
|
+
connect(signal) {
|
|
190
|
+
super.connect(signal);
|
|
191
|
+
this.bindGestures(this.control, signal, () => this.#grab());
|
|
192
|
+
document.fonts.addEventListener("loadingdone", () => {
|
|
193
|
+
this.renderMarkers();
|
|
194
|
+
}, { signal });
|
|
195
|
+
}
|
|
196
|
+
draw() {
|
|
197
|
+
this.surface()?.requestFrame();
|
|
198
|
+
}
|
|
199
|
+
isEmpty() {
|
|
200
|
+
return !this.#peaks || this.#peaks.samples.length === 0;
|
|
201
|
+
}
|
|
202
|
+
paint(context, { frameMs, isDirty, look, size }) {
|
|
203
|
+
const held = this.#held();
|
|
204
|
+
const timeline = frameTimeline({
|
|
205
|
+
clockSeconds: this.#clock.read(frameMs, {
|
|
206
|
+
isPlaying: this.playing,
|
|
207
|
+
playbackRate: this.playbackRate,
|
|
208
|
+
seconds: this.#sourceSeconds()
|
|
209
|
+
}),
|
|
210
|
+
frameMs,
|
|
211
|
+
...held ? { held } : {},
|
|
212
|
+
isPaged: look.isReducedMotion,
|
|
213
|
+
isPlaying: this.playing,
|
|
214
|
+
pending: this.#pending,
|
|
215
|
+
range: [this.min, Math.max(this.min, this.max)],
|
|
216
|
+
size,
|
|
217
|
+
zoom: this.zoom
|
|
218
|
+
});
|
|
219
|
+
const { seconds, view } = timeline;
|
|
220
|
+
this.#drawn = {
|
|
221
|
+
seconds,
|
|
222
|
+
startSeconds: view.startSeconds
|
|
223
|
+
};
|
|
224
|
+
placeLine(this.#playhead, timeline.playheadAt);
|
|
225
|
+
placeLine(this.#ghost, timeline.ghostAt);
|
|
226
|
+
this.toggleState("pending", view.pending.length > 0);
|
|
227
|
+
this.#rider.place({
|
|
228
|
+
playheadSeconds: seconds,
|
|
229
|
+
startSeconds: view.startSeconds,
|
|
230
|
+
widthPx: view.width / view.dpr,
|
|
231
|
+
windowSeconds: view.width / view.pixelsPerSecond
|
|
232
|
+
});
|
|
233
|
+
if (isDirty || timeline.paintKey !== this.#paintedKey) {
|
|
234
|
+
this.#paintedKey = timeline.paintKey;
|
|
235
|
+
this.#paintScene(context, {
|
|
236
|
+
...view,
|
|
237
|
+
colours: look.colours
|
|
238
|
+
});
|
|
239
|
+
if (timeline.wanted) this.#requestPeaks?.(...timeline.wanted);
|
|
240
|
+
}
|
|
241
|
+
if (timeline.isMoving) this.surface()?.requestFrame();
|
|
242
|
+
}
|
|
243
|
+
renderMarkers() {
|
|
244
|
+
const bounds = this.mapping().bounds;
|
|
245
|
+
this.#rider.measure(this.markers.flatMap(({ dimmed, label, start }) => label === void 0 || label === "" ? [] : [{
|
|
246
|
+
isDimmed: dimmed === true,
|
|
247
|
+
start: clamp(start, ...bounds),
|
|
248
|
+
text: label
|
|
249
|
+
}]));
|
|
250
|
+
this.surface()?.invalidate();
|
|
251
|
+
}
|
|
252
|
+
#grab() {
|
|
253
|
+
const drawn = this.#drawn ?? {
|
|
254
|
+
seconds: this.value,
|
|
255
|
+
startSeconds: this.value
|
|
256
|
+
};
|
|
257
|
+
this.#grabbed = drawn;
|
|
258
|
+
const mapping = this.mapping();
|
|
259
|
+
const [low, high] = mapping.bounds;
|
|
260
|
+
return {
|
|
261
|
+
fromProportion: mapping.proportionOf(drawn.seconds),
|
|
262
|
+
isKeptOnCancel: true,
|
|
263
|
+
position: (event) => -event.clientX,
|
|
264
|
+
travelPx: Math.max(1, (high - low) * this.zoom)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
#held() {
|
|
268
|
+
const grabbed = this.#grabbed;
|
|
269
|
+
const from = this.heldFrom();
|
|
270
|
+
if (!grabbed || from === void 0) return void 0;
|
|
271
|
+
return {
|
|
272
|
+
grabbed,
|
|
273
|
+
seconds: this.value === from ? grabbed.seconds : this.value
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
#paintScene(context, scene) {
|
|
277
|
+
const { dpr, height, pixelsPerSecond, startSeconds, width } = scene;
|
|
278
|
+
const x = (seconds) => (seconds - startSeconds) * pixelsPerSecond;
|
|
279
|
+
const { grid, marker } = scene.colours;
|
|
280
|
+
const line = Math.max(1, Math.round(dpr));
|
|
281
|
+
const [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];
|
|
282
|
+
const [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];
|
|
283
|
+
context.clearRect(0, 0, width, height);
|
|
284
|
+
paintHatch(context, scene, [0, from]);
|
|
285
|
+
paintHatch(context, scene, [to, width]);
|
|
286
|
+
context.fillStyle = grid;
|
|
287
|
+
if (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);
|
|
288
|
+
paintPlaceholders(context, scene);
|
|
289
|
+
if (this.#peaks) paintWave(context, scene, this.#peaks);
|
|
290
|
+
context.fillStyle = grid;
|
|
291
|
+
for (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);
|
|
292
|
+
context.fillStyle = marker;
|
|
293
|
+
for (const marker of this.markers) {
|
|
294
|
+
context.globalAlpha = marker.dimmed === true ? .5 : 1;
|
|
295
|
+
context.fillRect(x(marker.start) - line / 2, 0, line, height);
|
|
296
|
+
}
|
|
297
|
+
context.globalAlpha = 1;
|
|
298
|
+
}
|
|
299
|
+
#sourceSeconds() {
|
|
300
|
+
return this.#readTime?.() ?? this.playback();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
export { SonicWaveform };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { clampProportion } from "../lib/math.js";
|
|
2
|
+
import { requireChild, template } from "../lib/render.js";
|
|
3
|
+
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
import { SonicWaveElement } from "./wave-element.js";
|
|
5
|
+
import { createMarkerBand, nearestMarker } from "../lib/marker-band.js";
|
|
6
|
+
import { stripBars, stripRegions } from "../lib/strip-scene.js";
|
|
7
|
+
|
|
8
|
+
//#region src/elements/wavestrip.ts
|
|
9
|
+
const colours = {
|
|
10
|
+
buffered: "--_sonic-wavestrip-buffered",
|
|
11
|
+
played: "--_sonic-wavestrip-played",
|
|
12
|
+
scrub: "--_sonic-wavestrip-scrub",
|
|
13
|
+
wave: "--_sonic-wavestrip-wave"
|
|
14
|
+
};
|
|
15
|
+
const lengths = {
|
|
16
|
+
gapRatio: "--_sonic-wavestrip-bar-gap-ratio",
|
|
17
|
+
pitch: "--_sonic-wavestrip-bar-pitch",
|
|
18
|
+
radiusRatio: "--_sonic-wavestrip-bar-radius-ratio"
|
|
19
|
+
};
|
|
20
|
+
const cancelZonePx = 48;
|
|
21
|
+
const renderWavestrip = template(`
|
|
22
|
+
<div class="sonic-wavestrip" role="slider" tabindex="0">
|
|
23
|
+
<canvas class="sonic-wavestrip-canvas" aria-hidden="true"></canvas>
|
|
24
|
+
<div class="sonic-wavestrip-markers" aria-hidden="true"></div>
|
|
25
|
+
<div class="sonic-wavestrip-readout" popover="manual">
|
|
26
|
+
<span></span>
|
|
27
|
+
<input
|
|
28
|
+
autocomplete="off"
|
|
29
|
+
class="sonic-wavestrip-entry"
|
|
30
|
+
enterkeyhint="done"
|
|
31
|
+
hidden
|
|
32
|
+
spellcheck="false"
|
|
33
|
+
type="text"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
`, HTMLDivElement);
|
|
38
|
+
function fillRegion(context, path, [from, to, style]) {
|
|
39
|
+
context.save();
|
|
40
|
+
context.beginPath();
|
|
41
|
+
context.rect(from, 0, to - from, context.canvas.height);
|
|
42
|
+
context.clip();
|
|
43
|
+
context.fillStyle = style;
|
|
44
|
+
context.fill(path);
|
|
45
|
+
context.restore();
|
|
46
|
+
}
|
|
47
|
+
var SonicWavestrip = class extends SonicWaveElement {
|
|
48
|
+
get cancellable() {
|
|
49
|
+
return this.hasAttribute("cancellable");
|
|
50
|
+
}
|
|
51
|
+
set cancellable(isCancellable) {
|
|
52
|
+
this.reflect("cancellable", isCancellable);
|
|
53
|
+
}
|
|
54
|
+
get peaks() {
|
|
55
|
+
return this.#peaks;
|
|
56
|
+
}
|
|
57
|
+
set peaks(peaks) {
|
|
58
|
+
this.#peaks = peaks;
|
|
59
|
+
this.renderEmpty();
|
|
60
|
+
this.surface()?.invalidate();
|
|
61
|
+
}
|
|
62
|
+
control = renderWavestrip();
|
|
63
|
+
canvas = requireChild(this.control, ".sonic-wavestrip-canvas", HTMLCanvasElement);
|
|
64
|
+
colours = colours;
|
|
65
|
+
lengths = lengths;
|
|
66
|
+
sheet = "wavestrip.css";
|
|
67
|
+
#bars;
|
|
68
|
+
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
69
|
+
#paintedKey = "";
|
|
70
|
+
#peaks;
|
|
71
|
+
connectedCallback() {
|
|
72
|
+
this.upgradeProperties("cancellable", "peaks");
|
|
73
|
+
super.connectedCallback();
|
|
74
|
+
}
|
|
75
|
+
connect(signal) {
|
|
76
|
+
super.connect(signal);
|
|
77
|
+
this.#bindHover(this.control, signal);
|
|
78
|
+
this.bindGestures(this.control, signal, (event) => this.#grab(event));
|
|
79
|
+
}
|
|
80
|
+
draw() {
|
|
81
|
+
const surface = this.surface();
|
|
82
|
+
this.#placeReadout();
|
|
83
|
+
if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
|
|
84
|
+
}
|
|
85
|
+
isEmpty() {
|
|
86
|
+
return !this.#peaks || this.#peaks.length === 0;
|
|
87
|
+
}
|
|
88
|
+
paint(context, frame) {
|
|
89
|
+
const { look, size } = frame;
|
|
90
|
+
const strip = this.#stripRegions(size.width);
|
|
91
|
+
const bars = this.#barsFor(frame);
|
|
92
|
+
this.#paintedKey = strip.key;
|
|
93
|
+
context.clearRect(0, 0, size.width, size.height);
|
|
94
|
+
if (!bars) return;
|
|
95
|
+
context.fillStyle = look.colours.wave;
|
|
96
|
+
context.fill(bars);
|
|
97
|
+
for (const { from, kind, to } of strip.regions) fillRegion(context, bars, [
|
|
98
|
+
from,
|
|
99
|
+
to,
|
|
100
|
+
look.colours[kind]
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
renderMarkers() {
|
|
104
|
+
this.#markerBand.render(this.markers, this.markerProportions());
|
|
105
|
+
}
|
|
106
|
+
scrubsKeyRepeat() {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
#axis() {
|
|
110
|
+
const box = this.canvas.getBoundingClientRect();
|
|
111
|
+
return {
|
|
112
|
+
position: (event) => event.clientX,
|
|
113
|
+
startPx: box.left,
|
|
114
|
+
travelPx: Math.max(1, box.width)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
#barsFor({ isDirty, look, size }) {
|
|
118
|
+
const peaks = this.#peaks;
|
|
119
|
+
if (!peaks || peaks.length === 0) return void 0;
|
|
120
|
+
if (!isDirty && this.#bars) return this.#bars;
|
|
121
|
+
const path = new Path2D();
|
|
122
|
+
for (const { height, radius, width, x, y } of stripBars(peaks, size, look.lengths)) path.roundRect(x, y, width, height, radius);
|
|
123
|
+
this.#bars = path;
|
|
124
|
+
return path;
|
|
125
|
+
}
|
|
126
|
+
#bindHover(strip, signal) {
|
|
127
|
+
strip.addEventListener("pointermove", (event) => {
|
|
128
|
+
if (event.pointerType === "touch" || event.buttons !== 0) return;
|
|
129
|
+
if (!this.readout || this.isDisabled()) return;
|
|
130
|
+
const axis = this.#axis();
|
|
131
|
+
const at = clampProportion((event.clientX - axis.startPx) / axis.travelPx);
|
|
132
|
+
this.hoverReadout(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));
|
|
133
|
+
this.#placeReadout();
|
|
134
|
+
}, { signal });
|
|
135
|
+
for (const type of ["pointerdown", "pointerleave"]) strip.addEventListener(type, () => {
|
|
136
|
+
this.hoverReadout(void 0);
|
|
137
|
+
}, { signal });
|
|
138
|
+
}
|
|
139
|
+
#grab(event) {
|
|
140
|
+
const { startPx, ...axis } = this.#axis();
|
|
141
|
+
const at = (event.clientX - startPx) / axis.travelPx;
|
|
142
|
+
const outside = this.#outside();
|
|
143
|
+
const grabbed = {
|
|
144
|
+
...axis,
|
|
145
|
+
fromProportion: clampProportion(at)
|
|
146
|
+
};
|
|
147
|
+
this.input(this.#markerAt(at, axis.travelPx) ?? this.mapping().valueAt(at));
|
|
148
|
+
return outside ? {
|
|
149
|
+
...grabbed,
|
|
150
|
+
outside
|
|
151
|
+
} : grabbed;
|
|
152
|
+
}
|
|
153
|
+
#markerAt(at, travelPx) {
|
|
154
|
+
const markers = this.markers;
|
|
155
|
+
if (markers.length === 0) return void 0;
|
|
156
|
+
const { proportionOf } = this.mapping();
|
|
157
|
+
const index = nearestMarker(at, markers.map(({ start }) => proportionOf(start)), this.#markerBand.reachPx() / travelPx);
|
|
158
|
+
return index === void 0 ? void 0 : markers[index]?.start;
|
|
159
|
+
}
|
|
160
|
+
#outside() {
|
|
161
|
+
if (!this.cancellable) return void 0;
|
|
162
|
+
const box = this.control.getBoundingClientRect();
|
|
163
|
+
const zone = readPxProperty(getComputedStyle(this.control), "--_sonic-cancel-zone", cancelZonePx);
|
|
164
|
+
return (event) => event.clientY < box.top - zone || event.clientY > box.bottom + zone;
|
|
165
|
+
}
|
|
166
|
+
#placeReadout() {
|
|
167
|
+
const at = this.mapping().proportionOf(this.readoutValue());
|
|
168
|
+
this.control.style.setProperty("--_sonic-wavestrip-readout-at", String(at));
|
|
169
|
+
}
|
|
170
|
+
#stripRegions(width) {
|
|
171
|
+
const { proportionOf } = this.mapping();
|
|
172
|
+
const isScrubbing = this.heldFrom() !== void 0 && this.isRevealed();
|
|
173
|
+
return stripRegions({
|
|
174
|
+
buffered: this.buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
|
|
175
|
+
played: proportionOf(this.playback()),
|
|
176
|
+
...isScrubbing ? { scrub: proportionOf(this.value) } : {}
|
|
177
|
+
}, width);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
export { SonicWavestrip };
|