@xsynaptic/sonic-ui 0.16.0 → 0.18.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/CHANGELOG.md +20 -0
- package/README.md +112 -19
- package/dist/default/dat.js +1 -0
- package/dist/default/elements/button.js +2 -0
- package/dist/default/elements/dial.js +2 -0
- package/dist/default/elements/display-element.js +9 -7
- package/dist/default/elements/envelope.js +31 -72
- package/dist/default/elements/field-gesture.js +81 -0
- package/dist/default/elements/meter.js +4 -0
- package/dist/default/elements/position-group.js +42 -26
- package/dist/default/elements/radio-group.js +59 -56
- package/dist/default/elements/scale.js +3 -3
- package/dist/default/elements/slider.js +11 -13
- package/dist/default/elements/split.js +14 -13
- package/dist/default/elements/switch.js +31 -46
- package/dist/default/elements/toggle.js +20 -44
- package/dist/default/elements/value-element.js +65 -91
- package/dist/default/elements/value-gestures.js +22 -22
- package/dist/default/elements/wave-element.js +9 -12
- package/dist/default/elements/waveform.js +64 -249
- package/dist/default/elements/wavestrip.js +10 -5
- package/dist/default/elements/xy.js +67 -112
- package/dist/default/lib/canvas-surface.js +25 -0
- package/dist/default/lib/crossfade.js +1 -0
- package/dist/default/lib/envelope-curve.js +1 -0
- package/dist/default/lib/marker-rider.js +37 -14
- package/dist/default/lib/peaks-request.js +44 -0
- package/dist/default/lib/positions.js +58 -0
- package/dist/default/lib/scrub.js +12 -0
- package/dist/default/lib/value-model.js +62 -0
- package/dist/default/lib/waveform-scene.js +108 -0
- package/dist/default/lib/zoom-gesture.js +97 -1
- package/dist/dev/dat.d.ts +1 -0
- package/dist/dev/dat.js +1 -0
- package/dist/dev/dat.js.map +1 -1
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +2 -0
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.d.ts +2 -0
- package/dist/dev/elements/dial.js +2 -0
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +5 -3
- package/dist/dev/elements/display-element.js +9 -7
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +31 -72
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +82 -0
- package/dist/dev/elements/field-gesture.js.map +1 -0
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +4 -0
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/position-group.d.ts +12 -6
- package/dist/dev/elements/position-group.js +42 -26
- package/dist/dev/elements/position-group.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +11 -7
- package/dist/dev/elements/radio-group.js +59 -56
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/scale.js +3 -3
- package/dist/dev/elements/scale.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +4 -2
- package/dist/dev/elements/slider.js +11 -13
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/split.d.ts +1 -0
- package/dist/dev/elements/split.js +14 -13
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.d.ts +2 -2
- package/dist/dev/elements/switch.js +31 -46
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +20 -44
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +5 -5
- package/dist/dev/elements/value-element.js +65 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +22 -22
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +7 -4
- package/dist/dev/elements/wave-element.js +9 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +23 -22
- package/dist/dev/elements/waveform.js +64 -249
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +5 -1
- package/dist/dev/elements/wavestrip.js +10 -5
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +67 -112
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/lib/canvas-surface.d.ts +1 -0
- package/dist/dev/lib/canvas-surface.js +25 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +1 -0
- package/dist/dev/lib/crossfade.js +1 -0
- package/dist/dev/lib/crossfade.js.map +1 -1
- package/dist/dev/lib/envelope-curve.d.ts +1 -0
- package/dist/dev/lib/envelope-curve.js +1 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -1
- package/dist/dev/lib/marker-rider.js +37 -14
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/peaks-request.d.ts +5 -0
- package/dist/dev/lib/peaks-request.js +45 -0
- package/dist/dev/lib/peaks-request.js.map +1 -0
- package/dist/dev/lib/positions.d.ts +7 -0
- package/dist/dev/lib/positions.js +59 -0
- package/dist/dev/lib/positions.js.map +1 -0
- package/dist/dev/lib/scrub.d.ts +8 -0
- package/dist/dev/lib/scrub.js +13 -0
- package/dist/dev/lib/scrub.js.map +1 -0
- package/dist/dev/lib/time-regions.d.ts +1 -0
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/dev/lib/units.d.ts +8 -0
- package/dist/dev/lib/value-model.js +63 -0
- package/dist/dev/lib/value-model.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +1 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -1
- package/dist/dev/lib/waveform-scene.d.ts +11 -0
- package/dist/dev/lib/waveform-scene.js +109 -0
- package/dist/dev/lib/waveform-scene.js.map +1 -0
- package/dist/dev/lib/zoom-gesture.js +97 -1
- package/dist/dev/lib/zoom-gesture.js.map +1 -1
- package/dist/styles/button.css +16 -6
- package/dist/styles/waveform.css +12 -4
- package/package.json +1 -1
- package/dist/default/lib/asked-value.js +0 -21
- package/dist/default/lib/fill-watch.js +0 -29
- package/dist/dev/lib/asked-value.js +0 -22
- package/dist/dev/lib/asked-value.js.map +0 -1
- package/dist/dev/lib/fill-watch.js +0 -30
- package/dist/dev/lib/fill-watch.js.map +0 -1
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
import { clamp, roundTo } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
|
-
import { capturePointer } from "../lib/pointer-drag.js";
|
|
4
3
|
import { dueRegions, readRegions } from "../lib/time-regions.js";
|
|
5
4
|
import { SonicWaveElement } from "./wave-element.js";
|
|
6
5
|
import { frameTimeline } from "../lib/frame-timeline.js";
|
|
7
6
|
import { isKind, writeKind } from "../lib/marker-band.js";
|
|
8
7
|
import { createLabelRider } from "../lib/marker-rider.js";
|
|
8
|
+
import { createPeaksRequest } from "../lib/peaks-request.js";
|
|
9
9
|
import { createTrackingClock } from "../lib/tracking-clock.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { paintWaveform, waveformColours } from "../lib/waveform-scene.js";
|
|
11
|
+
import { bindZoom, bindZoomKeys } from "../lib/zoom-gesture.js";
|
|
12
12
|
|
|
13
13
|
//#region src/elements/waveform.ts
|
|
14
|
-
const colours = {
|
|
15
|
-
ends: "--_sonic-waveform-ends",
|
|
16
|
-
grid: "--_sonic-waveform-grid",
|
|
17
|
-
marker: "--_sonic-waveform-marker",
|
|
18
|
-
placeholder: "--_sonic-waveform-placeholder",
|
|
19
|
-
wave: "--_sonic-waveform-wave",
|
|
20
|
-
waveEdge: "--_sonic-waveform-wave-edge"
|
|
21
|
-
};
|
|
22
14
|
const defaultZoom = 70;
|
|
23
15
|
const defaultZoomMin = 20;
|
|
24
16
|
const defaultZoomMax = 280;
|
|
25
|
-
const zoomKeys = /* @__PURE__ */ new Map([
|
|
26
|
-
["+", 1],
|
|
27
|
-
["-", -1],
|
|
28
|
-
["=", 1]
|
|
29
|
-
]);
|
|
30
|
-
const amplitudeMargin = .94;
|
|
31
|
-
const hatchPitchPx = 8;
|
|
32
|
-
const placeholderWavelengthPx = 48;
|
|
33
|
-
const placeholderAmplitude = .2;
|
|
34
17
|
const renderWaveform = template(`
|
|
35
18
|
<div class="sonic-waveform" role="slider" tabindex="0">
|
|
36
19
|
<canvas class="sonic-waveform-canvas" aria-hidden="true"></canvas>
|
|
@@ -59,66 +42,6 @@ function placeLine(line, at) {
|
|
|
59
42
|
const translate = `${String(at * 100)}cqi`;
|
|
60
43
|
if (line.style.getPropertyValue("translate") !== translate) line.style.setProperty("translate", translate);
|
|
61
44
|
}
|
|
62
|
-
function paintHatch(context, scene, [from, to]) {
|
|
63
|
-
if (to <= from) return;
|
|
64
|
-
const { dpr, height } = scene;
|
|
65
|
-
const pitch = Math.max(2, Math.round(hatchPitchPx * dpr));
|
|
66
|
-
const offset = scene.startSeconds * scene.pixelsPerSecond;
|
|
67
|
-
context.save();
|
|
68
|
-
context.beginPath();
|
|
69
|
-
context.rect(from, 0, to - from, height);
|
|
70
|
-
context.clip();
|
|
71
|
-
context.beginPath();
|
|
72
|
-
for (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {
|
|
73
|
-
context.moveTo(x, height);
|
|
74
|
-
context.lineTo(x + height, 0);
|
|
75
|
-
}
|
|
76
|
-
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
77
|
-
context.strokeStyle = scene.colours.ends;
|
|
78
|
-
context.stroke();
|
|
79
|
-
context.restore();
|
|
80
|
-
}
|
|
81
|
-
function paintPlaceholders(context, scene) {
|
|
82
|
-
if (scene.pending.length === 0) return;
|
|
83
|
-
const { dpr, height, pixelsPerSecond, startSeconds } = scene;
|
|
84
|
-
const wavelength = placeholderWavelengthPx * dpr;
|
|
85
|
-
const center = height / 2;
|
|
86
|
-
const origin = startSeconds * pixelsPerSecond;
|
|
87
|
-
const waveY = (x) => center - Math.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) * center * placeholderAmplitude;
|
|
88
|
-
context.beginPath();
|
|
89
|
-
for (const [from, to] of scene.pending) {
|
|
90
|
-
const opening = (from - startSeconds) * pixelsPerSecond;
|
|
91
|
-
const closing = (to - startSeconds) * pixelsPerSecond;
|
|
92
|
-
context.moveTo(opening, waveY(opening));
|
|
93
|
-
for (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));
|
|
94
|
-
context.lineTo(closing, waveY(closing));
|
|
95
|
-
}
|
|
96
|
-
context.lineWidth = Math.max(1, Math.round(dpr));
|
|
97
|
-
context.strokeStyle = scene.colours.placeholder;
|
|
98
|
-
context.stroke();
|
|
99
|
-
}
|
|
100
|
-
function waveFill(context, scene) {
|
|
101
|
-
const { wave, waveEdge } = scene.colours;
|
|
102
|
-
const gradient = context.createLinearGradient(0, 0, 0, scene.height);
|
|
103
|
-
gradient.addColorStop(0, waveEdge);
|
|
104
|
-
gradient.addColorStop(.5, wave);
|
|
105
|
-
gradient.addColorStop(1, waveEdge);
|
|
106
|
-
return gradient;
|
|
107
|
-
}
|
|
108
|
-
function paintWave(context, scene, peaks) {
|
|
109
|
-
const buckets = waveformBuckets(peaks, scene);
|
|
110
|
-
const [first] = buckets;
|
|
111
|
-
if (!first) return;
|
|
112
|
-
const center = scene.height / 2;
|
|
113
|
-
const amplitudePx = center * amplitudeMargin;
|
|
114
|
-
context.fillStyle = waveFill(context, scene);
|
|
115
|
-
context.beginPath();
|
|
116
|
-
context.moveTo(first.x, center);
|
|
117
|
-
for (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);
|
|
118
|
-
for (const bucket of buckets.toReversed()) context.lineTo(bucket.x, center - bucket.low * amplitudePx);
|
|
119
|
-
context.closePath();
|
|
120
|
-
context.fill();
|
|
121
|
-
}
|
|
122
45
|
var SonicWaveform = class extends SonicWaveElement {
|
|
123
46
|
static observedAttributes = [
|
|
124
47
|
...SonicWaveElement.observedAttributes,
|
|
@@ -137,8 +60,9 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
137
60
|
set peaks(peaks) {
|
|
138
61
|
this.#peaks = peaks;
|
|
139
62
|
this.renderEmpty();
|
|
140
|
-
this.#
|
|
63
|
+
this.#peaksRequest.changed();
|
|
141
64
|
}
|
|
65
|
+
/** Spans being fetched; drawn as placeholders until cleared */
|
|
142
66
|
get pending() {
|
|
143
67
|
return this.#pending.map(([start, end]) => [start, end]);
|
|
144
68
|
}
|
|
@@ -152,8 +76,9 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
152
76
|
const key = region.join(":");
|
|
153
77
|
return [key, listedMs.get(key) ?? nowMs];
|
|
154
78
|
}));
|
|
155
|
-
this.#
|
|
79
|
+
this.#peaksRequest.changed();
|
|
156
80
|
}
|
|
81
|
+
/** How long a span stays listed in `pending` before its placeholder draws */
|
|
157
82
|
get pendingDelay() {
|
|
158
83
|
return Math.max(0, this.numberAttribute("pending-delay", 0));
|
|
159
84
|
}
|
|
@@ -173,6 +98,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
173
98
|
set playing(isPlaying) {
|
|
174
99
|
this.reflect("playing", isPlaying);
|
|
175
100
|
}
|
|
101
|
+
/** Read every frame for the playback time, so the waveform scrolls smoother than writes to `value` arrive */
|
|
176
102
|
get readTime() {
|
|
177
103
|
return this.#readTime;
|
|
178
104
|
}
|
|
@@ -186,13 +112,19 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
186
112
|
set reducedMotion(motion) {
|
|
187
113
|
this.reflect("reduced-motion", motion);
|
|
188
114
|
}
|
|
115
|
+
get renderLabel() {
|
|
116
|
+
return this.#renderLabel;
|
|
117
|
+
}
|
|
118
|
+
set renderLabel(render) {
|
|
119
|
+
this.#renderLabel = render;
|
|
120
|
+
if (this.isBound()) this.renderMarkers();
|
|
121
|
+
}
|
|
189
122
|
get requestPeaks() {
|
|
190
123
|
return this.#requestPeaks;
|
|
191
124
|
}
|
|
192
125
|
set requestPeaks(request) {
|
|
193
126
|
this.#requestPeaks = request;
|
|
194
|
-
this.#
|
|
195
|
-
this.surface()?.invalidate();
|
|
127
|
+
this.#peaksRequest.reset();
|
|
196
128
|
}
|
|
197
129
|
get zoom() {
|
|
198
130
|
const zoom = this.numberAttribute("zoom", defaultZoom);
|
|
@@ -223,56 +155,58 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
223
155
|
}
|
|
224
156
|
control = renderWaveform();
|
|
225
157
|
canvas = requireChild(this.control, ".sonic-waveform-canvas", HTMLCanvasElement);
|
|
226
|
-
colours =
|
|
158
|
+
colours = waveformColours;
|
|
227
159
|
lengths = {};
|
|
228
160
|
sheet = "waveform.css";
|
|
229
161
|
sizeProperty = "--_sonic-waveform-size";
|
|
230
|
-
#asked = /* @__PURE__ */ new WeakSet();
|
|
231
|
-
#askedKey;
|
|
232
162
|
#clock = createTrackingClock();
|
|
233
|
-
#connection;
|
|
234
163
|
#drawn;
|
|
235
164
|
#ghost = requireChild(this.control, ".sonic-waveform-ghost", HTMLDivElement);
|
|
236
165
|
#grabbed;
|
|
237
|
-
#isAsking = false;
|
|
238
|
-
#isPinchSpent = false;
|
|
239
166
|
#kindColours = /* @__PURE__ */ new Map();
|
|
240
167
|
#paintedKey = "";
|
|
241
168
|
#peaks;
|
|
169
|
+
#peaksRequest = createPeaksRequest(() => {
|
|
170
|
+
this.surface()?.invalidate();
|
|
171
|
+
});
|
|
242
172
|
#pending = [];
|
|
243
173
|
#pendingListedMs = /* @__PURE__ */ new Map();
|
|
244
|
-
#pinch;
|
|
245
174
|
#playhead = requireChild(this.control, ".sonic-waveform-playhead", HTMLDivElement);
|
|
246
175
|
#readTime;
|
|
176
|
+
#renderLabel;
|
|
247
177
|
#requestPeaks;
|
|
248
178
|
#rider = createLabelRider(this.control, {
|
|
249
179
|
className: "sonic-waveform-label",
|
|
250
180
|
colourProperty: "--_sonic-waveform-marker",
|
|
251
|
-
|
|
181
|
+
fadeProperties: ["--_sonic-waveform-label-fade-start", "--_sonic-waveform-label-fade-end"],
|
|
252
182
|
insetProperty: "--_sonic-waveform-label-inset",
|
|
253
183
|
parkProperty: "--_sonic-waveform-label-park"
|
|
254
184
|
});
|
|
255
|
-
#
|
|
256
|
-
#wheel;
|
|
185
|
+
#zoomGesture;
|
|
257
186
|
attributeChangedCallback(name) {
|
|
258
187
|
super.attributeChangedCallback(name);
|
|
259
|
-
if (name === "zoomable") this.#
|
|
188
|
+
if (name === "zoomable") this.#zoomGesture?.sync();
|
|
260
189
|
}
|
|
261
190
|
connectedCallback() {
|
|
262
|
-
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
191
|
+
this.upgradeProperties("peaks", "pending", "pendingDelay", "playing", "playbackRate", "readTime", "reducedMotion", "renderLabel", "requestPeaks", "zoom", "zoomable", "zoomMax", "zoomMin");
|
|
263
192
|
super.connectedCallback();
|
|
264
193
|
}
|
|
265
194
|
connect(signal) {
|
|
266
195
|
super.connect(signal);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
196
|
+
const zoomTarget = {
|
|
197
|
+
claim: () => {
|
|
198
|
+
this.abandonHold();
|
|
199
|
+
},
|
|
200
|
+
isDisabled: () => this.isDisabled(),
|
|
201
|
+
isZoomable: () => this.zoomable,
|
|
202
|
+
zoom: () => this.zoom,
|
|
203
|
+
zoomTo: (target) => {
|
|
204
|
+
this.#zoomTo(target);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
this.#zoomGesture = bindZoom(this.control, zoomTarget, signal);
|
|
274
208
|
this.bindGestures(this.control, signal, () => this.#grab());
|
|
275
|
-
|
|
209
|
+
bindZoomKeys(this.control, zoomTarget, signal);
|
|
276
210
|
if (!("fonts" in document)) return;
|
|
277
211
|
document.fonts.addEventListener("loadingdone", () => {
|
|
278
212
|
this.renderMarkers();
|
|
@@ -288,7 +222,9 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
288
222
|
}
|
|
289
223
|
paint(context, { frameMs, isDirty, isRebuilt, look, size }) {
|
|
290
224
|
const held = this.#held();
|
|
225
|
+
const isPaged = look.isReducedMotion && this.reducedMotion === "page";
|
|
291
226
|
const pending = this.#duePending();
|
|
227
|
+
const range = [this.min, Math.max(this.min, this.max)];
|
|
292
228
|
const timeline = frameTimeline({
|
|
293
229
|
clockSeconds: this.#clock.read(frameMs, {
|
|
294
230
|
isPlaying: this.playing,
|
|
@@ -297,11 +233,11 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
297
233
|
}),
|
|
298
234
|
frameMs,
|
|
299
235
|
...held ? { held } : {},
|
|
300
|
-
isPaged
|
|
236
|
+
isPaged,
|
|
301
237
|
isPlaying: this.playing,
|
|
302
238
|
isStill: look.isReducedMotion,
|
|
303
239
|
pending,
|
|
304
|
-
range
|
|
240
|
+
range,
|
|
305
241
|
size,
|
|
306
242
|
zoom: this.zoom
|
|
307
243
|
});
|
|
@@ -315,6 +251,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
315
251
|
this.showCurrentMarker(seconds);
|
|
316
252
|
this.toggleState("pending", view.pending.length > 0);
|
|
317
253
|
this.#rider.place({
|
|
254
|
+
isPaged,
|
|
318
255
|
playheadSeconds: seconds,
|
|
319
256
|
startSeconds: view.startSeconds,
|
|
320
257
|
widthPx: view.width / view.dpr,
|
|
@@ -322,113 +259,41 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
322
259
|
});
|
|
323
260
|
if (isDirty || timeline.paintKey !== this.#paintedKey) {
|
|
324
261
|
this.#paintedKey = timeline.paintKey;
|
|
325
|
-
this.#
|
|
262
|
+
if (isRebuilt) this.#kindColours = this.#readKindColours();
|
|
263
|
+
paintWaveform(context, {
|
|
326
264
|
...view,
|
|
327
265
|
colours: look.colours
|
|
328
|
-
},
|
|
329
|
-
|
|
266
|
+
}, {
|
|
267
|
+
kindColours: this.#kindColours,
|
|
268
|
+
markers: this.markerList(),
|
|
269
|
+
peaks: this.#peaks,
|
|
270
|
+
range
|
|
271
|
+
});
|
|
272
|
+
this.#peaksRequest.ask(this.#requestPeaks, timeline.wanted);
|
|
330
273
|
}
|
|
331
274
|
if (timeline.isMoving) this.surface()?.requestFrame();
|
|
332
275
|
}
|
|
333
276
|
renderMarkers() {
|
|
334
277
|
const bounds = this.mapping().bounds;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
278
|
+
const render = this.#renderLabel;
|
|
279
|
+
this.#rider.measure(this.markers.flatMap((marker) => marker.label === void 0 || marker.label === "" ? [] : [{
|
|
280
|
+
isDimmed: marker.dimmed === true,
|
|
281
|
+
...marker.kind === void 0 ? {} : { kind: marker.kind },
|
|
282
|
+
start: clamp(marker.start, ...bounds),
|
|
283
|
+
text: marker.label,
|
|
284
|
+
...render ? { write: (element) => {
|
|
285
|
+
render(marker, element);
|
|
286
|
+
} } : {}
|
|
340
287
|
}]));
|
|
341
288
|
this.surface()?.rebuild();
|
|
342
289
|
}
|
|
343
|
-
#ask(wanted) {
|
|
344
|
-
const key = wanted.join(":");
|
|
345
|
-
if (key === this.#askedKey) return;
|
|
346
|
-
let asked;
|
|
347
|
-
this.#askedKey = key;
|
|
348
|
-
this.#isAsking = true;
|
|
349
|
-
try {
|
|
350
|
-
asked = this.#requestPeaks?.(...wanted);
|
|
351
|
-
} finally {
|
|
352
|
-
this.#isAsking = false;
|
|
353
|
-
}
|
|
354
|
-
const promises = asked instanceof Promise ? [asked] : asked ?? [];
|
|
355
|
-
for (const promise of promises) {
|
|
356
|
-
if (this.#asked.has(promise)) continue;
|
|
357
|
-
this.#asked.add(promise);
|
|
358
|
-
this.#repaintAfter(promise);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
#bindPinch(signal) {
|
|
362
|
-
const control = this.control;
|
|
363
|
-
this.#touches.clear();
|
|
364
|
-
this.#pinch = void 0;
|
|
365
|
-
this.#isPinchSpent = false;
|
|
366
|
-
control.addEventListener("pointerdown", (event) => {
|
|
367
|
-
if (event.pointerType !== "touch" || !this.zoomable || this.isDisabled()) return;
|
|
368
|
-
this.#touches.set(event.pointerId, event.clientX);
|
|
369
|
-
if (this.#pinch || this.#isPinchSpent) return;
|
|
370
|
-
const pointerIds = this.#touchPair();
|
|
371
|
-
if (!pointerIds) return;
|
|
372
|
-
this.abandonHold();
|
|
373
|
-
capturePointer(control, event.pointerId);
|
|
374
|
-
this.#pinch = {
|
|
375
|
-
pointerIds,
|
|
376
|
-
startSpanPx: this.#spanPx(pointerIds),
|
|
377
|
-
startZoom: this.zoom
|
|
378
|
-
};
|
|
379
|
-
}, { signal });
|
|
380
|
-
control.addEventListener("pointermove", (event) => {
|
|
381
|
-
if (!this.#touches.has(event.pointerId)) return;
|
|
382
|
-
this.#touches.set(event.pointerId, event.clientX);
|
|
383
|
-
const pinch = this.#pinch;
|
|
384
|
-
if (!pinch?.pointerIds.includes(event.pointerId)) return;
|
|
385
|
-
this.#zoomTo(pinch.startZoom * pinchFactor(pinch.startSpanPx, this.#spanPx(pinch.pointerIds)));
|
|
386
|
-
}, { signal });
|
|
387
|
-
for (const type of ["pointerup", "pointercancel"]) control.addEventListener(type, (event) => {
|
|
388
|
-
if (!this.#touches.delete(event.pointerId)) return;
|
|
389
|
-
if (this.#pinch?.pointerIds.includes(event.pointerId)) {
|
|
390
|
-
this.#pinch = void 0;
|
|
391
|
-
this.#isPinchSpent = true;
|
|
392
|
-
}
|
|
393
|
-
if (this.#touches.size === 0) this.#isPinchSpent = false;
|
|
394
|
-
}, { signal });
|
|
395
|
-
}
|
|
396
|
-
#bindWheel() {
|
|
397
|
-
this.#wheel?.abort();
|
|
398
|
-
this.#wheel = void 0;
|
|
399
|
-
if (!this.zoomable || !this.#connection) return;
|
|
400
|
-
this.#wheel = new AbortController();
|
|
401
|
-
this.control.addEventListener("wheel", (event) => {
|
|
402
|
-
if (!event.ctrlKey && !event.metaKey || this.isDisabled()) return;
|
|
403
|
-
event.preventDefault();
|
|
404
|
-
this.#zoomTo(this.zoom * wheelFactor(event.deltaY, event.deltaMode));
|
|
405
|
-
}, {
|
|
406
|
-
passive: false,
|
|
407
|
-
signal: this.#wheel.signal
|
|
408
|
-
});
|
|
409
|
-
let startZoom = this.zoom;
|
|
410
|
-
for (const type of ["gesturestart", "gesturechange"]) this.control.addEventListener(type, (event) => {
|
|
411
|
-
if (this.isDisabled() || this.#touches.size > 0) return;
|
|
412
|
-
event.preventDefault();
|
|
413
|
-
if (type === "gesturestart") startZoom = this.zoom;
|
|
414
|
-
else if ("scale" in event && typeof event.scale === "number") this.#zoomTo(startZoom * event.scale);
|
|
415
|
-
}, { signal: this.#wheel.signal });
|
|
416
|
-
}
|
|
417
|
-
#bindZoomKeys(signal) {
|
|
418
|
-
this.control.addEventListener("keydown", (event) => {
|
|
419
|
-
const direction = this.#zoomKey(event);
|
|
420
|
-
if (direction === void 0) return;
|
|
421
|
-
event.preventDefault();
|
|
422
|
-
this.#zoomTo(this.zoom * zoomKeyFactor ** direction);
|
|
423
|
-
}, { signal });
|
|
424
|
-
}
|
|
425
290
|
#duePending() {
|
|
426
291
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
427
292
|
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
428
293
|
return due;
|
|
429
294
|
}
|
|
430
295
|
#grab() {
|
|
431
|
-
if (this.#
|
|
296
|
+
if (this.#zoomGesture?.isPinching() === true) return void 0;
|
|
432
297
|
const drawn = this.#drawn ?? {
|
|
433
298
|
seconds: this.value,
|
|
434
299
|
startSeconds: this.value
|
|
@@ -445,42 +310,13 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
445
310
|
}
|
|
446
311
|
#held() {
|
|
447
312
|
const grabbed = this.#grabbed;
|
|
448
|
-
const from = this.
|
|
313
|
+
const { from } = this.scrubState();
|
|
449
314
|
if (!grabbed || from === void 0) return void 0;
|
|
450
315
|
return {
|
|
451
316
|
grabbed,
|
|
452
317
|
seconds: this.value === from ? grabbed.seconds : this.value
|
|
453
318
|
};
|
|
454
319
|
}
|
|
455
|
-
#paintMarkers(context, scene, isRebuilt) {
|
|
456
|
-
if (isRebuilt) this.#kindColours = this.#readKindColours();
|
|
457
|
-
const line = Math.max(1, Math.round(scene.dpr));
|
|
458
|
-
for (const { dimmed, kind, start } of this.markers) {
|
|
459
|
-
const x = (start - scene.startSeconds) * scene.pixelsPerSecond;
|
|
460
|
-
context.fillStyle = (isKind(kind) ? this.#kindColours.get(kind) : void 0) ?? scene.colours.marker;
|
|
461
|
-
context.globalAlpha = dimmed === true ? .5 : 1;
|
|
462
|
-
context.fillRect(x - line / 2, 0, line, scene.height);
|
|
463
|
-
}
|
|
464
|
-
context.globalAlpha = 1;
|
|
465
|
-
}
|
|
466
|
-
#paintScene(context, scene, isRebuilt) {
|
|
467
|
-
const { dpr, height, pixelsPerSecond, startSeconds, width } = scene;
|
|
468
|
-
const x = (seconds) => (seconds - startSeconds) * pixelsPerSecond;
|
|
469
|
-
const { ends, grid } = scene.colours;
|
|
470
|
-
const line = Math.max(1, Math.round(dpr));
|
|
471
|
-
const [opening, closing] = [x(this.min), x(Math.max(this.min, this.max))];
|
|
472
|
-
const [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];
|
|
473
|
-
context.clearRect(0, 0, width, height);
|
|
474
|
-
paintHatch(context, scene, [0, from]);
|
|
475
|
-
paintHatch(context, scene, [to, width]);
|
|
476
|
-
context.fillStyle = grid;
|
|
477
|
-
if (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);
|
|
478
|
-
paintPlaceholders(context, scene);
|
|
479
|
-
if (this.#peaks) paintWave(context, scene, this.#peaks);
|
|
480
|
-
context.fillStyle = ends;
|
|
481
|
-
for (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);
|
|
482
|
-
this.#paintMarkers(context, scene, isRebuilt);
|
|
483
|
-
}
|
|
484
320
|
#readKindColours() {
|
|
485
321
|
const kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];
|
|
486
322
|
const probes = kinds.map((kind) => {
|
|
@@ -494,29 +330,8 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
494
330
|
for (const probe of probes) probe.remove();
|
|
495
331
|
return new Map(kinds.map((kind, index) => [kind, read[index] ?? ""]));
|
|
496
332
|
}
|
|
497
|
-
#repaint() {
|
|
498
|
-
if (!this.#isAsking) this.#askedKey = void 0;
|
|
499
|
-
this.surface()?.invalidate();
|
|
500
|
-
}
|
|
501
|
-
async #repaintAfter(asked) {
|
|
502
|
-
await Promise.allSettled([asked]);
|
|
503
|
-
this.#repaint();
|
|
504
|
-
}
|
|
505
333
|
#sourceSeconds() {
|
|
506
|
-
return this.#readTime?.() ?? this.
|
|
507
|
-
}
|
|
508
|
-
#spanPx([first, second]) {
|
|
509
|
-
return (this.#touches.get(second) ?? 0) - (this.#touches.get(first) ?? 0);
|
|
510
|
-
}
|
|
511
|
-
#touchPair() {
|
|
512
|
-
const [first, second, third] = this.#touches.keys();
|
|
513
|
-
if (first === void 0 || second === void 0 || third !== void 0) return void 0;
|
|
514
|
-
return [first, second];
|
|
515
|
-
}
|
|
516
|
-
#zoomKey(event) {
|
|
517
|
-
if (!this.zoomable || this.isDisabled() || event.target !== this.control) return void 0;
|
|
518
|
-
if (event.ctrlKey || event.metaKey || event.altKey) return void 0;
|
|
519
|
-
return zoomKeys.get(event.key);
|
|
334
|
+
return this.#readTime?.() ?? this.scrubState().played;
|
|
520
335
|
}
|
|
521
336
|
#zoomTo(target) {
|
|
522
337
|
const next = roundTo(clamp(target, this.zoomMin, this.zoomMax), 2);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { clampProportion } from "../lib/math.js";
|
|
2
2
|
import { requireChild, template } from "../lib/render.js";
|
|
3
3
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
4
|
+
import { scrubRegions } from "../lib/scrub.js";
|
|
4
5
|
import { sortedRegions } from "../lib/time-regions.js";
|
|
5
6
|
import { SonicWaveElement } from "./wave-element.js";
|
|
6
7
|
import { createMarkerBand } from "../lib/marker-band.js";
|
|
@@ -48,6 +49,7 @@ function fillRegion(context, path, [from, to, style]) {
|
|
|
48
49
|
context.restore();
|
|
49
50
|
}
|
|
50
51
|
var SonicWavestrip = class extends SonicWaveElement {
|
|
52
|
+
/** Loaded spans, in seconds */
|
|
51
53
|
get buffered() {
|
|
52
54
|
return this.#buffered.map(([start, end]) => [start, end]);
|
|
53
55
|
}
|
|
@@ -61,6 +63,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
61
63
|
set cancellable(isCancellable) {
|
|
62
64
|
this.reflect("cancellable", isCancellable);
|
|
63
65
|
}
|
|
66
|
+
/** One amplitude from 0 to 1 per slice of the whole track, any length; resampled to the bars and never normalised */
|
|
64
67
|
get peaks() {
|
|
65
68
|
return this.#peaks;
|
|
66
69
|
}
|
|
@@ -80,6 +83,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
80
83
|
#markerBand = createMarkerBand(requireChild(this.control, ".sonic-wavestrip-markers", HTMLDivElement));
|
|
81
84
|
#paintedKey = "";
|
|
82
85
|
#peaks;
|
|
86
|
+
/** Viewport x of a time, clamped to the strip */
|
|
83
87
|
clientXOf(value) {
|
|
84
88
|
const { startPx, travelPx } = this.#axis();
|
|
85
89
|
return startPx + this.mapping().proportionOf(value) * travelPx;
|
|
@@ -107,7 +111,7 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
107
111
|
draw() {
|
|
108
112
|
const surface = this.surface();
|
|
109
113
|
this.#placeReadout();
|
|
110
|
-
this.showCurrentMarker(this.
|
|
114
|
+
this.showCurrentMarker(this.scrubState().played);
|
|
111
115
|
if (surface && this.#stripRegions(surface.size.width).key !== this.#paintedKey) surface.requestFrame();
|
|
112
116
|
}
|
|
113
117
|
isEmpty() {
|
|
@@ -189,12 +193,13 @@ var SonicWavestrip = class extends SonicWaveElement {
|
|
|
189
193
|
this.control.style.setProperty("--_sonic-wavestrip-readout-at", String(at));
|
|
190
194
|
}
|
|
191
195
|
#stripRegions(width) {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
196
|
+
const mapping = this.mapping();
|
|
197
|
+
const { proportionOf } = mapping;
|
|
198
|
+
const { played, scrub } = scrubRegions(this.scrubState(), this.value, mapping);
|
|
194
199
|
return stripRegions({
|
|
195
200
|
buffered: this.#buffered.map(([start, end]) => [proportionOf(start), proportionOf(end)]),
|
|
196
|
-
played
|
|
197
|
-
...
|
|
201
|
+
played,
|
|
202
|
+
...scrub === void 0 ? {} : { scrub }
|
|
198
203
|
}, width);
|
|
199
204
|
}
|
|
200
205
|
};
|