@xsynaptic/sonic-ui 0.6.0 → 0.8.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 +4 -0
- package/dist/default/define/spectrum.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/button.js +33 -4
- package/dist/default/elements/dial.js +5 -1
- package/dist/default/elements/display-element.js +77 -0
- package/dist/default/elements/envelope.js +15 -3
- package/dist/default/elements/meter.js +1 -1
- package/dist/default/elements/radio-group.js +3 -2
- package/dist/default/elements/readout-claim.js +0 -29
- package/dist/default/elements/scale.js +43 -0
- package/dist/default/elements/slider.js +82 -3
- package/dist/default/elements/sonic-element.js +2 -67
- package/dist/default/elements/spectrum.js +230 -0
- package/dist/default/elements/switch.js +1 -1
- package/dist/default/elements/value-element.js +81 -91
- package/dist/default/elements/wave-element.js +59 -9
- package/dist/default/elements/waveform.js +56 -10
- package/dist/default/elements/wavestrip.js +45 -21
- package/dist/default/elements/xy.js +3 -2
- package/dist/default/index.js +2 -1
- package/dist/default/lib/canvas-surface.js +7 -4
- package/dist/default/lib/frame-timeline.js +2 -2
- package/dist/default/lib/hover-preview.js +46 -0
- package/dist/default/lib/marker-rider.js +7 -4
- package/dist/default/lib/mirror-children.js +64 -0
- package/dist/default/lib/owned-control.js +10 -0
- package/dist/default/lib/pointer-drag.js +7 -2
- package/dist/default/lib/spectrum-columns.js +74 -0
- package/dist/default/lib/spectrum-levels.js +55 -0
- package/dist/default/lib/strip-scene.js +12 -1
- package/dist/default/lib/time-regions.js +8 -1
- package/dist/dev/define/spectrum.d.ts +2 -0
- package/dist/dev/define/spectrum.js +8 -0
- package/dist/dev/define/spectrum.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/button.d.ts +2 -0
- package/dist/dev/elements/button.js +33 -4
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +5 -1
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.d.ts +29 -0
- package/dist/dev/elements/display-element.js +78 -0
- package/dist/dev/elements/display-element.js.map +1 -0
- package/dist/dev/elements/envelope.js +15 -3
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/radio-group.js +3 -2
- package/dist/dev/elements/radio-group.js.map +1 -1
- package/dist/dev/elements/readout-claim.js +0 -29
- package/dist/dev/elements/readout-claim.js.map +1 -1
- package/dist/dev/elements/scale.js +44 -0
- package/dist/dev/elements/scale.js.map +1 -0
- package/dist/dev/elements/slider.d.ts +9 -0
- package/dist/dev/elements/slider.js +82 -3
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +0 -8
- package/dist/dev/elements/sonic-element.js +3 -90
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/spectrum.d.ts +59 -0
- package/dist/dev/elements/spectrum.js +231 -0
- package/dist/dev/elements/spectrum.js.map +1 -0
- package/dist/dev/elements/switch.js +1 -1
- package/dist/dev/elements/value-element.d.ts +8 -6
- package/dist/dev/elements/value-element.js +81 -91
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +8 -2
- package/dist/dev/elements/wave-element.js +59 -9
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +12 -4
- package/dist/dev/elements/waveform.js +56 -10
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +7 -0
- package/dist/dev/elements/wavestrip.js +45 -21
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -2
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +4 -1
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/canvas-surface.d.ts +4 -3
- package/dist/dev/lib/canvas-surface.js +7 -4
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/frame-timeline.js +2 -2
- package/dist/dev/lib/frame-timeline.js.map +1 -1
- package/dist/dev/lib/hover-preview.js +47 -0
- package/dist/dev/lib/hover-preview.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +7 -4
- package/dist/dev/lib/marker-rider.js.map +1 -1
- package/dist/dev/lib/mirror-children.js +80 -0
- package/dist/dev/lib/mirror-children.js.map +1 -0
- package/dist/dev/lib/owned-control.js +23 -0
- package/dist/dev/lib/owned-control.js.map +1 -0
- package/dist/dev/lib/pointer-drag.js +7 -2
- package/dist/dev/lib/pointer-drag.js.map +1 -1
- package/dist/dev/lib/spectrum-columns.js +75 -0
- package/dist/dev/lib/spectrum-columns.js.map +1 -0
- package/dist/dev/lib/spectrum-levels.js +56 -0
- package/dist/dev/lib/spectrum-levels.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +12 -1
- package/dist/dev/lib/strip-scene.js.map +1 -1
- package/dist/dev/lib/time-regions.js +8 -1
- package/dist/dev/lib/time-regions.js.map +1 -1
- package/dist/skins/flat.css +2 -1
- package/dist/styles/button.css +28 -1
- package/dist/styles/controls.css +1 -0
- package/dist/styles/envelope.css +30 -30
- package/dist/styles/led.css +18 -2
- package/dist/styles/material.css +90 -29
- package/dist/styles/meter.css +22 -18
- package/dist/styles/panel.css +2 -1
- package/dist/styles/slider.css +42 -5
- package/dist/styles/spectrum.css +105 -0
- package/dist/styles/switch.css +10 -7
- package/dist/styles/waveform.css +38 -16
- package/dist/styles/wavestrip.css +22 -4
- package/dist/styles/xy.css +14 -30
- package/package.json +2 -1
|
@@ -4,7 +4,6 @@ import { readPxProperty } from "./read-px-property.js";
|
|
|
4
4
|
import { writeKind } from "./marker-band.js";
|
|
5
5
|
|
|
6
6
|
//#region src/lib/marker-rider.ts
|
|
7
|
-
const fadeShare = .25;
|
|
8
7
|
function lineX(seconds, view) {
|
|
9
8
|
return (seconds - view.startSeconds) / view.windowSeconds * view.widthPx;
|
|
10
9
|
}
|
|
@@ -16,7 +15,7 @@ function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
|
16
15
|
const approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
|
|
17
16
|
return {
|
|
18
17
|
index,
|
|
19
|
-
opacity: clamp(approachPx / (view.widthPx * fadeShare), 0, 1),
|
|
18
|
+
opacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,
|
|
20
19
|
shownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),
|
|
21
20
|
x
|
|
22
21
|
};
|
|
@@ -91,15 +90,18 @@ function measureWidths(control, className, texts) {
|
|
|
91
90
|
return widths;
|
|
92
91
|
}
|
|
93
92
|
function createLabelRider(control, options) {
|
|
94
|
-
const { className, colourProperty, insetProperty } = options;
|
|
93
|
+
const { className, colourProperty, fadeProperty, insetProperty } = options;
|
|
95
94
|
const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
|
|
96
95
|
const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
|
|
96
|
+
let fadeShare = .25;
|
|
97
97
|
let insetPx = 0;
|
|
98
98
|
let measured = [];
|
|
99
99
|
return {
|
|
100
100
|
measure: (labels) => {
|
|
101
101
|
const widths = measureWidths(control, className, labels.map(({ text }) => text));
|
|
102
|
-
|
|
102
|
+
const styles = getComputedStyle(control);
|
|
103
|
+
fadeShare = readPxProperty(styles, fadeProperty, .25);
|
|
104
|
+
insetPx = readPxProperty(styles, insetProperty, 0);
|
|
103
105
|
measured = labels.map((label, index) => ({
|
|
104
106
|
...label,
|
|
105
107
|
widthPx: widths[index] ?? 0
|
|
@@ -108,6 +110,7 @@ function createLabelRider(control, options) {
|
|
|
108
110
|
place: (window) => {
|
|
109
111
|
const layout = layoutRider(measured, {
|
|
110
112
|
...window,
|
|
113
|
+
fadeShare,
|
|
111
114
|
insetPx
|
|
112
115
|
});
|
|
113
116
|
writeLabel(parked, [measured, colourProperty], layout.parked);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marker-rider.js","names":[],"sources":["../../../src/lib/marker-rider.ts"],"sourcesContent":["import { writeKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\n\ninterface RiderWindow {\n\tplayheadSeconds: number;\n\tstartSeconds: number;\n\twidthPx: number;\n\twindowSeconds: number;\n}\n\nexport interface RiderView extends RiderWindow {\n\tinsetPx: number;\n}\n\nexport interface RiderLabel {\n\tisDimmed: boolean;\n\tkind?: string;\n\tstart: number;\n\ttext: string;\n\twidthPx: number;\n}\n\ninterface LabelPlacement {\n\tindex: number;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\ninterface RiderLayout {\n\tarriving?: LabelPlacement;\n\tparked?: LabelPlacement;\n}\n\ninterface LabelSlot {\n\telement: HTMLElement;\n\tkind: string | undefined;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\
|
|
1
|
+
{"version":3,"file":"marker-rider.js","names":[],"sources":["../../../src/lib/marker-rider.ts"],"sourcesContent":["import { writeKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\n\ninterface RiderWindow {\n\tplayheadSeconds: number;\n\tstartSeconds: number;\n\twidthPx: number;\n\twindowSeconds: number;\n}\n\nexport interface RiderView extends RiderWindow {\n\tfadeShare: number;\n\tinsetPx: number;\n}\n\nexport interface RiderLabel {\n\tisDimmed: boolean;\n\tkind?: string;\n\tstart: number;\n\ttext: string;\n\twidthPx: number;\n}\n\ninterface LabelPlacement {\n\tindex: number;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\ninterface RiderLayout {\n\tarriving?: LabelPlacement;\n\tparked?: LabelPlacement;\n}\n\ninterface LabelSlot {\n\telement: HTMLElement;\n\tkind: string | undefined;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\nfunction lineX(seconds: number, view: RiderView): number {\n\treturn ((seconds - view.startSeconds) / view.windowSeconds) * view.widthPx;\n}\n\nfunction parkedPlacement(\n\tlabels: ReadonlyArray<RiderLabel>,\n\tindex: number,\n\t[view, arrivingX]: [RiderView, number],\n): LabelPlacement | undefined {\n\tconst parked = labels[index];\n\tif (!parked) return undefined;\n\n\tconst next = labels[index + 1];\n\tconst x = Math.max(view.insetPx, lineX(parked.start, view) + view.insetPx);\n\tconst approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;\n\n\treturn {\n\t\tindex,\n\t\topacity: view.fadeShare > 0 ? clamp(approachPx / (view.widthPx * view.fadeShare), 0, 1) : 1,\n\t\tshownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),\n\t\tx,\n\t};\n}\n\nfunction isDrawable(length: number): boolean {\n\treturn Number.isFinite(length) && length > 0;\n}\n\nexport function layoutRider(labels: ReadonlyArray<RiderLabel>, view: RiderView): RiderLayout {\n\tif (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};\n\n\tconst parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);\n\tconst next = labels[parkedIndex + 1];\n\tconst arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;\n\tconst parked = parkedPlacement(labels, parkedIndex, [view, arrivingX]);\n\tconst arriving: LabelPlacement | undefined =\n\t\tnext && arrivingX < view.widthPx\n\t\t\t? {\n\t\t\t\t\tindex: parkedIndex + 1,\n\t\t\t\t\topacity: 1,\n\t\t\t\t\tshownPx: Math.min(next.widthPx, view.widthPx - arrivingX),\n\t\t\t\t\tx: arrivingX,\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn { ...(arriving ? { arriving } : {}), ...(parked ? { parked } : {}) };\n}\n\nfunction labelSlot(element: HTMLElement): LabelSlot {\n\treturn { element, kind: undefined, opacity: NaN, shownPx: NaN, x: NaN };\n}\n\nfunction writePlacement(\n\tslot: LabelSlot,\n\t{ opacity, shownPx, x }: LabelPlacement,\n\twidthPx: number,\n): void {\n\tconst { style } = slot.element;\n\n\tif (slot.x !== x) {\n\t\tslot.x = x;\n\t\tstyle.setProperty('translate', `${x.toFixed(2)}px`);\n\t}\n\tif (slot.shownPx !== shownPx) {\n\t\tslot.shownPx = shownPx;\n\t\tstyle.setProperty(\n\t\t\t'clip-path',\n\t\t\tshownPx < widthPx ? `inset(0 calc(100% - ${shownPx.toFixed(2)}px) 0 0)` : '',\n\t\t);\n\t}\n\tif (slot.opacity !== opacity) {\n\t\tslot.opacity = opacity;\n\t\tstyle.setProperty('opacity', opacity < 1 ? opacity.toFixed(3) : '');\n\t}\n}\n\nfunction writeLabel(\n\tslot: LabelSlot,\n\t[labels, colourProperty]: [ReadonlyArray<RiderLabel>, string],\n\tplacement: LabelPlacement | undefined,\n): void {\n\tconst { element } = slot;\n\tconst label = placement && placement.shownPx > 0 ? labels[placement.index] : undefined;\n\n\tif (element.hidden !== !label) element.hidden = !label;\n\tif (!label || !placement) return;\n\n\tif (element.textContent !== label.text) element.textContent = label.text;\n\telement.toggleAttribute('data-sonic-dimmed', label.isDimmed);\n\tif (slot.kind !== label.kind) {\n\t\tslot.kind = label.kind;\n\t\twriteKind(element, label.kind, colourProperty);\n\t}\n\twritePlacement(slot, placement, label.widthPx);\n}\n\nfunction measureWidths(\n\tcontrol: HTMLElement,\n\tclassName: string,\n\ttexts: Array<string>,\n): Array<number> {\n\tconst probes = texts.map((text) => {\n\t\tconst probe = document.createElement('div');\n\n\t\tprobe.className = className;\n\t\tprobe.textContent = text;\n\n\t\treturn probe;\n\t});\n\n\tcontrol.append(...probes);\n\n\tconst widths = probes.map((probe) => probe.getBoundingClientRect().width);\n\n\tfor (const probe of probes) probe.remove();\n\n\treturn widths;\n}\n\nexport function createLabelRider(\n\tcontrol: HTMLElement,\n\toptions: {\n\t\tclassName: string;\n\t\tcolourProperty: `--_sonic-${string}`;\n\t\tfadeProperty: `--_sonic-${string}`;\n\t\tinsetProperty: `--_sonic-${string}`;\n\t},\n): {\n\tmeasure: (labels: ReadonlyArray<Omit<RiderLabel, 'widthPx'>>) => void;\n\tplace: (window: RiderWindow) => void;\n} {\n\tconst { className, colourProperty, fadeProperty, insetProperty } = options;\n\tconst parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));\n\tconst arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));\n\tlet fadeShare = 0.25;\n\tlet insetPx = 0;\n\tlet measured: Array<RiderLabel> = [];\n\n\treturn {\n\t\tmeasure: (labels) => {\n\t\t\tconst widths = measureWidths(\n\t\t\t\tcontrol,\n\t\t\t\tclassName,\n\t\t\t\tlabels.map(({ text }) => text),\n\t\t\t);\n\n\t\t\tconst styles = getComputedStyle(control);\n\n\t\t\tfadeShare = readPxProperty(styles, fadeProperty, 0.25);\n\t\t\tinsetPx = readPxProperty(styles, insetProperty, 0);\n\t\t\tmeasured = labels\n\t\t\t\t.map((label, index) => ({ ...label, widthPx: widths[index] ?? 0 }))\n\t\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\t},\n\t\tplace: (window) => {\n\t\t\tconst layout = layoutRider(measured, { ...window, fadeShare, insetPx });\n\n\t\t\twriteLabel(parked, [measured, colourProperty], layout.parked);\n\t\t\twriteLabel(arriving, [measured, colourProperty], layout.arriving);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { appendOnce, checkChildren } from "./owned-control.js";
|
|
2
|
+
import { attachSlots } from "./render.js";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/mirror-children.ts
|
|
5
|
+
const checkedMirrors = /* @__PURE__ */ new WeakSet();
|
|
6
|
+
const hostSlots = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
const interactive = "a[href], button, input, select, textarea, [tabindex]";
|
|
8
|
+
function unmirrorable(original) {
|
|
9
|
+
if (!(original instanceof Element)) return void 0;
|
|
10
|
+
return [original, ...original.querySelectorAll("*")].find((element) => element.matches(interactive) || element.localName.includes("-"));
|
|
11
|
+
}
|
|
12
|
+
function checkMirrored(host, originals) {
|
|
13
|
+
if (checkedMirrors.has(host.constructor)) return;
|
|
14
|
+
const [found] = originals.flatMap((original) => unmirrorable(original) ?? []);
|
|
15
|
+
if (!found) return;
|
|
16
|
+
checkedMirrors.add(host.constructor);
|
|
17
|
+
console.warn(`<${host.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`);
|
|
18
|
+
}
|
|
19
|
+
function childHolding(host, node) {
|
|
20
|
+
let current = node;
|
|
21
|
+
while (current && current.parentNode !== host) current = current.parentNode;
|
|
22
|
+
return current ?? void 0;
|
|
23
|
+
}
|
|
24
|
+
function isCopiedChange(host, record, { control, isCopied }) {
|
|
25
|
+
if (record.target === host) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
|
|
26
|
+
const child = childHolding(host, record.target);
|
|
27
|
+
if (child === void 0) return false;
|
|
28
|
+
return isCopied(child) || record.type === "attributes" && record.target === child;
|
|
29
|
+
}
|
|
30
|
+
function mirrorChildren(host, { control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
|
|
31
|
+
const slots = hostSlots.get(host) ?? attachSlots(host);
|
|
32
|
+
let copies = /* @__PURE__ */ new Map();
|
|
33
|
+
hostSlots.set(host, slots);
|
|
34
|
+
const mirror = (touched) => {
|
|
35
|
+
appendOnce(host, control);
|
|
36
|
+
const children = [...host.childNodes].filter((child) => child !== control);
|
|
37
|
+
const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
|
|
38
|
+
const kept = copies;
|
|
39
|
+
const originals = children.filter((child) => isCopied(child));
|
|
40
|
+
checkMirrored(host, originals);
|
|
41
|
+
checkChildren(host, children.filter((child) => !isCopied(child) && !isPassed(child)));
|
|
42
|
+
copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
|
|
43
|
+
place([...copies.values()].filter((made) => made !== void 0));
|
|
44
|
+
slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
|
|
45
|
+
slots.kept.assign(...slotted.filter((child) => isCopied(child)));
|
|
46
|
+
};
|
|
47
|
+
const watch = () => {
|
|
48
|
+
observer.disconnect();
|
|
49
|
+
observer.observe(host, { childList: true });
|
|
50
|
+
for (const child of host.childNodes) {
|
|
51
|
+
if (child === control) continue;
|
|
52
|
+
observer.observe(child, {
|
|
53
|
+
attributes: true,
|
|
54
|
+
characterData: true,
|
|
55
|
+
childList: true,
|
|
56
|
+
subtree: true
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const observer = new MutationObserver((records) => {
|
|
61
|
+
const changes = records.filter((record) => isCopiedChange(host, record, {
|
|
62
|
+
control,
|
|
63
|
+
isCopied
|
|
64
|
+
}));
|
|
65
|
+
if (changes.length === 0) return;
|
|
66
|
+
if (changes.some((record) => record.target === host)) {
|
|
67
|
+
mirror();
|
|
68
|
+
watch();
|
|
69
|
+
} else mirror(new Set(changes.map((record) => childHolding(host, record.target))));
|
|
70
|
+
});
|
|
71
|
+
mirror();
|
|
72
|
+
watch();
|
|
73
|
+
signal.addEventListener("abort", () => {
|
|
74
|
+
observer.disconnect();
|
|
75
|
+
}, { once: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { mirrorChildren };
|
|
80
|
+
//# sourceMappingURL=mirror-children.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mirror-children.js","names":[],"sources":["../../../src/lib/mirror-children.ts"],"sourcesContent":["import type { MirrorSlots } from '#lib/render.ts';\n\nimport { appendOnce, checkChildren } from '#lib/owned-control.ts';\nimport { attachSlots } from '#lib/render.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst checkedMirrors = new WeakSet<object>();\n\nconst hostSlots = new WeakMap<HTMLElement, MirrorSlots>();\n\nconst interactive = 'a[href], button, input, select, textarea, [tabindex]';\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (original: ChildNode) => Node | undefined;\n\tisCopied?: (child: Node) => boolean;\n\tisPassed?: (child: Node) => boolean;\n\tplace: (copies: Array<Node>) => void;\n}\n\nfunction unmirrorable(original: Node): Element | undefined {\n\tif (!(original instanceof Element)) return undefined;\n\n\treturn [original, ...original.querySelectorAll('*')].find(\n\t\t(element) => element.matches(interactive) || element.localName.includes('-'),\n\t);\n}\n\nfunction checkMirrored(host: HTMLElement, originals: Array<Node>): void {\n\tif (!__DEV__ || checkedMirrors.has(host.constructor)) return;\n\n\tconst [found] = originals.flatMap((original) => unmirrorable(original) ?? []);\n\tif (!found) return;\n\n\tcheckedMirrors.add(host.constructor);\n\tconsole.warn(\n\t\t`<${host.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`,\n\t);\n}\n\nfunction childHolding(host: HTMLElement, node: Node): Node | undefined {\n\tlet current: Node | null = node;\n\n\twhile (current && current.parentNode !== host) current = current.parentNode;\n\n\treturn current ?? undefined;\n}\n\nfunction isCopiedChange(\n\thost: HTMLElement,\n\trecord: MutationRecord,\n\t{ control, isCopied }: Required<Pick<Mirror, 'control' | 'isCopied'>>,\n): boolean {\n\tif (record.target === host) {\n\t\treturn (\n\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t);\n\t}\n\n\tconst child = childHolding(host, record.target);\n\n\tif (child === undefined) return false;\n\n\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n}\n\nexport function mirrorChildren(\n\thost: HTMLElement,\n\t{ control, copy, isCopied = () => true, isPassed = () => false, place }: Mirror,\n\tsignal: AbortSignal,\n): void {\n\tconst slots = hostSlots.get(host) ?? attachSlots(host);\n\tlet copies = new Map<ChildNode, Node | undefined>();\n\n\thostSlots.set(host, slots);\n\tconst mirror = (touched?: Set<Node | undefined>): void => {\n\t\tappendOnce(host, control);\n\n\t\tconst children = [...host.childNodes].filter((child) => child !== control);\n\t\tconst slotted = children.filter(\n\t\t\t(child): child is Element | Text => child instanceof Element || child instanceof Text,\n\t\t);\n\t\tconst kept = copies;\n\t\tconst originals = children.filter((child) => isCopied(child));\n\n\t\tif (__DEV__) {\n\t\t\tcheckMirrored(host, originals);\n\t\t\tcheckChildren(\n\t\t\t\thost,\n\t\t\t\tchildren.filter((child) => !isCopied(child) && !isPassed(child)),\n\t\t\t);\n\t\t}\n\t\tcopies = new Map(\n\t\t\toriginals.map((original) => [\n\t\t\t\toriginal,\n\t\t\t\ttouched && !touched.has(original) && kept.has(original)\n\t\t\t\t\t? kept.get(original)\n\t\t\t\t\t: copy(original),\n\t\t\t]),\n\t\t);\n\t\tplace([...copies.values()].filter((made) => made !== undefined));\n\t\tslots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);\n\t\tslots.kept.assign(...slotted.filter((child) => isCopied(child)));\n\t};\n\t// Each child is watched, never the control, so a value write wakes nothing\n\tconst watch = (): void => {\n\t\tobserver.disconnect();\n\t\tobserver.observe(host, { childList: true });\n\t\tfor (const child of host.childNodes) {\n\t\t\tif (child === control) continue;\n\n\t\t\tobserver.observe(child, {\n\t\t\t\tattributes: true,\n\t\t\t\tcharacterData: true,\n\t\t\t\tchildList: true,\n\t\t\t\tsubtree: true,\n\t\t\t});\n\t\t}\n\t};\n\tconst observer = new MutationObserver((records) => {\n\t\tconst changes = records.filter((record) => isCopiedChange(host, record, { control, isCopied }));\n\t\tif (changes.length === 0) return;\n\n\t\tif (changes.some((record) => record.target === host)) {\n\t\t\tmirror();\n\t\t\twatch();\n\t\t} else mirror(new Set(changes.map((record) => childHolding(host, record.target))));\n\t});\n\n\tmirror();\n\twatch();\n\tsignal.addEventListener(\n\t\t'abort',\n\t\t() => {\n\t\t\tobserver.disconnect();\n\t\t},\n\t\t{ once: true },\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/lib/owned-control.ts
|
|
2
|
+
const checkedChildren = /* @__PURE__ */ new WeakSet();
|
|
3
|
+
function isContent(child) {
|
|
4
|
+
if (child instanceof Element) return true;
|
|
5
|
+
return child instanceof Text && child.data.trim() !== "";
|
|
6
|
+
}
|
|
7
|
+
function appendOnce(host, control) {
|
|
8
|
+
const [hook] = control.classList;
|
|
9
|
+
const stale = [...host.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
|
|
10
|
+
for (const child of stale) child.remove();
|
|
11
|
+
if (control.parentNode !== host) host.append(control);
|
|
12
|
+
}
|
|
13
|
+
function checkChildren(host, undocumented) {
|
|
14
|
+
if (checkedChildren.has(host.constructor)) return;
|
|
15
|
+
const found = undocumented.find((child) => isContent(child));
|
|
16
|
+
if (!found) return;
|
|
17
|
+
checkedChildren.add(host.constructor);
|
|
18
|
+
console.warn(`<${host.localName}> uses only the children it documents, so the ${found instanceof Element ? `<${found.localName}>` : "text"} inside it is unsupported`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { appendOnce, checkChildren };
|
|
23
|
+
//# sourceMappingURL=owned-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owned-control.js","names":[],"sources":["../../../src/lib/owned-control.ts"],"sourcesContent":["// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst checkedChildren = new WeakSet<object>();\n\nfunction isContent(child: Node): boolean {\n\tif (child instanceof Element) return true;\n\n\treturn child instanceof Text && child.data.trim() !== '';\n}\n\nexport function appendOnce(host: HTMLElement, control: Element): void {\n\tconst [hook] = control.classList;\n\tconst stale = [...host.children].filter(\n\t\t(child) => child !== control && hook !== undefined && child.classList.contains(hook),\n\t);\n\n\tfor (const child of stale) child.remove();\n\tif (control.parentNode !== host) host.append(control);\n}\n\nexport function checkChildren(host: HTMLElement, undocumented: Array<Node>): void {\n\tif (!__DEV__ || checkedChildren.has(host.constructor)) return;\n\n\tconst found = undocumented.find((child) => isContent(child));\n\tif (!found) return;\n\n\tcheckedChildren.add(host.constructor);\n\tconsole.warn(\n\t\t`<${host.localName}> uses only the children it documents, so the ${\n\t\t\tfound instanceof Element ? `<${found.localName}>` : 'text'\n\t\t} inside it is unsupported`,\n\t);\n}\n"],"mappings":";AAGA,MAAM,kCAAkB,IAAI,QAAgB;AAE5C,SAAS,UAAU,OAAsB;CACxC,IAAI,iBAAiB,SAAS,OAAO;CAErC,OAAO,iBAAiB,QAAQ,MAAM,KAAK,KAAK,MAAM;AACvD;AAEA,SAAgB,WAAW,MAAmB,SAAwB;CACrE,MAAM,CAAC,QAAQ,QAAQ;CACvB,MAAM,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,QAC/B,UAAU,UAAU,WAAW,SAAS,UAAa,MAAM,UAAU,SAAS,IAAI,CACpF;CAEA,KAAK,MAAM,SAAS,OAAO,MAAM,OAAO;CACxC,IAAI,QAAQ,eAAe,MAAM,KAAK,OAAO,OAAO;AACrD;AAEA,SAAgB,cAAc,MAAmB,cAAiC;CACjF,IAAgB,gBAAgB,IAAI,KAAK,WAAW,GAAG;CAEvD,MAAM,QAAQ,aAAa,MAAM,UAAU,UAAU,KAAK,CAAC;CAC3D,IAAI,CAAC,OAAO;CAEZ,gBAAgB,IAAI,KAAK,WAAW;CACpC,QAAQ,KACP,IAAI,KAAK,UAAU,gDAClB,iBAAiB,UAAU,IAAI,MAAM,UAAU,KAAK,OACpD,0BACF;AACD"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
//#region src/lib/pointer-drag.ts
|
|
2
|
+
function capturePointer(target, pointerId) {
|
|
3
|
+
try {
|
|
4
|
+
target.setPointerCapture(pointerId);
|
|
5
|
+
} catch {}
|
|
6
|
+
}
|
|
2
7
|
function bindDrag(target, handlers, signal) {
|
|
3
8
|
let held;
|
|
4
9
|
const end = () => {
|
|
@@ -22,8 +27,8 @@ function bindDrag(target, handlers, signal) {
|
|
|
22
27
|
drag,
|
|
23
28
|
pointerId: event.pointerId
|
|
24
29
|
};
|
|
25
|
-
target.setPointerCapture(event.pointerId);
|
|
26
30
|
handlers.toggle?.(true);
|
|
31
|
+
capturePointer(target, event.pointerId);
|
|
27
32
|
}, { signal });
|
|
28
33
|
whileHeld("pointermove", handlers.move);
|
|
29
34
|
whileHeld("pointercancel", (drag) => {
|
|
@@ -43,5 +48,5 @@ function bindDrag(target, handlers, signal) {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
//#endregion
|
|
46
|
-
export { bindDrag };
|
|
51
|
+
export { bindDrag, capturePointer };
|
|
47
52
|
//# sourceMappingURL=pointer-drag.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pointer-drag.js","names":[],"sources":["../../../src/lib/pointer-drag.ts"],"sourcesContent":["interface DragHandlers<Drag> {\n\tcancel?: (drag: Drag) => void;\n\tgrab: (event: PointerEvent) => Drag | undefined;\n\tlift?: (drag: Drag, event: PointerEvent) => void;\n\tmove: (drag: Drag, event: PointerEvent) => void;\n\trelease?: (drag: Drag) => void;\n\ttoggle?: (isDragging: boolean) => void;\n}\n\nexport interface PointerDrag<Drag> {\n\tcurrent: () => Drag | undefined;\n\tend: () => void;\n}\n\nexport function bindDrag<Drag>(\n\ttarget: HTMLElement,\n\thandlers: DragHandlers<Drag>,\n\tsignal: AbortSignal,\n): PointerDrag<Drag> {\n\tlet held: undefined | { drag: Drag; pointerId: number };\n\n\tconst end = (): void => {\n\t\tif (!held) return;\n\n\t\tconst { drag } = held;\n\n\t\theld = undefined;\n\t\thandlers.toggle?.(false);\n\t\thandlers.release?.(drag);\n\t};\n\tconst whileHeld = (\n\t\ttype: 'lostpointercapture' | 'pointercancel' | 'pointermove' | 'pointerup',\n\t\thandle: (drag: Drag, event: PointerEvent) => void,\n\t): void => {\n\t\ttarget.addEventListener(\n\t\t\ttype,\n\t\t\t(event) => {\n\t\t\t\tif (held?.pointerId === event.pointerId) handle(held.drag, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t};\n\n\ttarget.addEventListener(\n\t\t'pointerdown',\n\t\t(event) => {\n\t\t\tif (event.button !== 0 || (held && held.pointerId !== event.pointerId)) return;\n\n\t\t\tend();\n\n\t\t\tconst drag = handlers.grab(event);\n\t\t\tif (drag === undefined) return;\n\n\t\t\theld = { drag, pointerId: event.pointerId };\n\t\t\
|
|
1
|
+
{"version":3,"file":"pointer-drag.js","names":[],"sources":["../../../src/lib/pointer-drag.ts"],"sourcesContent":["interface DragHandlers<Drag> {\n\tcancel?: (drag: Drag) => void;\n\tgrab: (event: PointerEvent) => Drag | undefined;\n\tlift?: (drag: Drag, event: PointerEvent) => void;\n\tmove: (drag: Drag, event: PointerEvent) => void;\n\trelease?: (drag: Drag) => void;\n\ttoggle?: (isDragging: boolean) => void;\n}\n\nexport interface PointerDrag<Drag> {\n\tcurrent: () => Drag | undefined;\n\tend: () => void;\n}\n\nexport function capturePointer(target: HTMLElement, pointerId: number): void {\n\ttry {\n\t\ttarget.setPointerCapture(pointerId);\n\t} catch {\n\t\t// A synthetic pointer has no id to capture; the gesture carries on uncaptured\n\t}\n}\n\nexport function bindDrag<Drag>(\n\ttarget: HTMLElement,\n\thandlers: DragHandlers<Drag>,\n\tsignal: AbortSignal,\n): PointerDrag<Drag> {\n\tlet held: undefined | { drag: Drag; pointerId: number };\n\n\tconst end = (): void => {\n\t\tif (!held) return;\n\n\t\tconst { drag } = held;\n\n\t\theld = undefined;\n\t\thandlers.toggle?.(false);\n\t\thandlers.release?.(drag);\n\t};\n\tconst whileHeld = (\n\t\ttype: 'lostpointercapture' | 'pointercancel' | 'pointermove' | 'pointerup',\n\t\thandle: (drag: Drag, event: PointerEvent) => void,\n\t): void => {\n\t\ttarget.addEventListener(\n\t\t\ttype,\n\t\t\t(event) => {\n\t\t\t\tif (held?.pointerId === event.pointerId) handle(held.drag, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t};\n\n\ttarget.addEventListener(\n\t\t'pointerdown',\n\t\t(event) => {\n\t\t\tif (event.button !== 0 || (held && held.pointerId !== event.pointerId)) return;\n\n\t\t\tend();\n\n\t\t\tconst drag = handlers.grab(event);\n\t\t\tif (drag === undefined) return;\n\n\t\t\theld = { drag, pointerId: event.pointerId };\n\t\t\thandlers.toggle?.(true);\n\t\t\tcapturePointer(target, event.pointerId);\n\t\t},\n\t\t{ signal },\n\t);\n\twhileHeld('pointermove', handlers.move);\n\twhileHeld('pointercancel', (drag) => {\n\t\thandlers.cancel?.(drag);\n\t\tend();\n\t});\n\twhileHeld('lostpointercapture', end);\n\twhileHeld('pointerup', (drag, event) => {\n\t\tend();\n\t\thandlers.lift?.(drag, event);\n\t});\n\tsignal.addEventListener('abort', end, { once: true });\n\n\treturn { current: () => held?.drag, end };\n}\n"],"mappings":";AAcA,SAAgB,eAAe,QAAqB,WAAyB;CAC5E,IAAI;EACH,OAAO,kBAAkB,SAAS;CACnC,QAAQ,CAER;AACD;AAEA,SAAgB,SACf,QACA,UACA,QACoB;CACpB,IAAI;CAEJ,MAAM,YAAkB;EACvB,IAAI,CAAC,MAAM;EAEX,MAAM,EAAE,SAAS;EAEjB,OAAO;EACP,SAAS,SAAS,KAAK;EACvB,SAAS,UAAU,IAAI;CACxB;CACA,MAAM,aACL,MACA,WACU;EACV,OAAO,iBACN,OACC,UAAU;GACV,IAAI,MAAM,cAAc,MAAM,WAAW,OAAO,KAAK,MAAM,KAAK;EACjE,GACA,EAAE,OAAO,CACV;CACD;CAEA,OAAO,iBACN,gBACC,UAAU;EACV,IAAI,MAAM,WAAW,KAAM,QAAQ,KAAK,cAAc,MAAM,WAAY;EAExE,IAAI;EAEJ,MAAM,OAAO,SAAS,KAAK,KAAK;EAChC,IAAI,SAAS,QAAW;EAExB,OAAO;GAAE;GAAM,WAAW,MAAM;EAAU;EAC1C,SAAS,SAAS,IAAI;EACtB,eAAe,QAAQ,MAAM,SAAS;CACvC,GACA,EAAE,OAAO,CACV;CACA,UAAU,eAAe,SAAS,IAAI;CACtC,UAAU,kBAAkB,SAAS;EACpC,SAAS,SAAS,IAAI;EACtB,IAAI;CACL,CAAC;CACD,UAAU,sBAAsB,GAAG;CACnC,UAAU,cAAc,MAAM,UAAU;EACvC,IAAI;EACJ,SAAS,OAAO,MAAM,KAAK;CAC5B,CAAC;CACD,OAAO,iBAAiB,SAAS,KAAK,EAAE,MAAM,KAAK,CAAC;CAEpD,OAAO;EAAE,eAAe,MAAM;EAAM;CAAI;AACzC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { linearTaper, logTaper } from "./taper.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/spectrum-columns.ts
|
|
4
|
+
const gridDecibels = 12;
|
|
5
|
+
function binHertz({ binCount, sampleRate }) {
|
|
6
|
+
return sampleRate / 2 / binCount;
|
|
7
|
+
}
|
|
8
|
+
function columnEdges(axis, width) {
|
|
9
|
+
const edges = new Float64Array(width + 1);
|
|
10
|
+
const taper = logTaper(axis.frequencyMin, axis.frequencyMax);
|
|
11
|
+
if (!taper) return edges;
|
|
12
|
+
const hertz = binHertz(axis);
|
|
13
|
+
for (let edge = 0; edge <= width; edge += 1) edges[edge] = taper.valueAt(edge / width) / hertz;
|
|
14
|
+
return edges;
|
|
15
|
+
}
|
|
16
|
+
function columnLevels(columns, levels, { edges, floor }) {
|
|
17
|
+
const levelAt = (bin) => bin < levels.length ? levels[bin] ?? floor : floor;
|
|
18
|
+
for (let column = 0; column < columns.length; column += 1) {
|
|
19
|
+
const from = edges[column] ?? 0;
|
|
20
|
+
const to = edges[column + 1] ?? from;
|
|
21
|
+
const first = Math.ceil(from);
|
|
22
|
+
const last = Math.ceil(to) - 1;
|
|
23
|
+
if (first > last) {
|
|
24
|
+
const below = levelAt(first - 1);
|
|
25
|
+
columns[column] = below + (levelAt(first) - below) * ((from + to) / 2 - (first - 1));
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let loudest = floor;
|
|
29
|
+
for (let bin = first; bin <= last && bin < levels.length; bin += 1) loudest = Math.max(loudest, levelAt(bin));
|
|
30
|
+
columns[column] = loudest;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function levelRows({ max, min }, height) {
|
|
34
|
+
const taper = linearTaper(min, max);
|
|
35
|
+
return (level) => Math.round((1 - taper.proportionOf(level)) * height);
|
|
36
|
+
}
|
|
37
|
+
function gridColumns({ frequencyMax, frequencyMin }, width) {
|
|
38
|
+
const taper = logTaper(frequencyMin, frequencyMax);
|
|
39
|
+
const columns = [];
|
|
40
|
+
if (!taper) return columns;
|
|
41
|
+
for (let exponent = Math.floor(Math.log10(frequencyMin)) + 1; 10 ** exponent < frequencyMax; exponent += 1) columns.push(Math.round(taper.proportionOf(10 ** exponent) * width));
|
|
42
|
+
return columns;
|
|
43
|
+
}
|
|
44
|
+
function gridRows({ max, min }, rowOf) {
|
|
45
|
+
const rows = [];
|
|
46
|
+
for (let level = (Math.floor(min / gridDecibels) + 1) * gridDecibels; level < max; level += gridDecibels) rows.push(rowOf(level));
|
|
47
|
+
return rows;
|
|
48
|
+
}
|
|
49
|
+
function zoneRows({ clipFrom, hotFrom }, rowOf) {
|
|
50
|
+
const clip = rowOf(clipFrom);
|
|
51
|
+
return {
|
|
52
|
+
clip,
|
|
53
|
+
hot: Math.max(clip, rowOf(hotFrom))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function loudestBin(frame, axis) {
|
|
57
|
+
const hertz = binHertz(axis);
|
|
58
|
+
const last = Math.min(Math.floor(axis.frequencyMax / hertz), frame.length - 1);
|
|
59
|
+
let loudest = -1;
|
|
60
|
+
let decibels = -Infinity;
|
|
61
|
+
for (let bin = Math.ceil(axis.frequencyMin / hertz); bin <= last; bin += 1) {
|
|
62
|
+
const level = frame[bin] ?? NaN;
|
|
63
|
+
if (!(Number.isFinite(level) && level > decibels)) continue;
|
|
64
|
+
loudest = bin;
|
|
65
|
+
decibels = level;
|
|
66
|
+
}
|
|
67
|
+
return loudest === -1 ? void 0 : {
|
|
68
|
+
decibels,
|
|
69
|
+
frequency: loudest * hertz
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { columnEdges, columnLevels, gridColumns, gridRows, levelRows, loudestBin, zoneRows };
|
|
75
|
+
//# sourceMappingURL=spectrum-columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectrum-columns.js","names":[],"sources":["../../../src/lib/spectrum-columns.ts"],"sourcesContent":["import { linearTaper, logTaper } from '#lib/taper.ts';\n\nexport interface FrequencyAxis {\n\tbinCount: number;\n\tfrequencyMax: number;\n\tfrequencyMin: number;\n\tsampleRate: number;\n}\n\ninterface LevelRange {\n\tmax: number;\n\tmin: number;\n}\n\nconst gridDecibels = 12;\n\nfunction binHertz({ binCount, sampleRate }: FrequencyAxis): number {\n\treturn sampleRate / 2 / binCount;\n}\n\nexport function columnEdges(axis: FrequencyAxis, width: number): Float64Array {\n\tconst edges = new Float64Array(width + 1);\n\tconst taper = logTaper(axis.frequencyMin, axis.frequencyMax);\n\tif (!taper) return edges;\n\n\tconst hertz = binHertz(axis);\n\n\tfor (let edge = 0; edge <= width; edge += 1) edges[edge] = taper.valueAt(edge / width) / hertz;\n\n\treturn edges;\n}\n\nexport function columnLevels(\n\tcolumns: Float64Array,\n\tlevels: ArrayLike<number>,\n\t{ edges, floor }: { edges: Float64Array; floor: number },\n): void {\n\tconst levelAt = (bin: number): number => (bin < levels.length ? (levels[bin] ?? floor) : floor);\n\n\tfor (let column = 0; column < columns.length; column += 1) {\n\t\tconst from = edges[column] ?? 0;\n\t\tconst to = edges[column + 1] ?? from;\n\t\tconst first = Math.ceil(from);\n\t\tconst last = Math.ceil(to) - 1;\n\n\t\tif (first > last) {\n\t\t\tconst below = levelAt(first - 1);\n\n\t\t\tcolumns[column] = below + (levelAt(first) - below) * ((from + to) / 2 - (first - 1));\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet loudest = floor;\n\n\t\tfor (let bin = first; bin <= last && bin < levels.length; bin += 1) {\n\t\t\tloudest = Math.max(loudest, levelAt(bin));\n\t\t}\n\t\tcolumns[column] = loudest;\n\t}\n}\n\nexport function levelRows({ max, min }: LevelRange, height: number): (level: number) => number {\n\tconst taper = linearTaper(min, max);\n\n\treturn (level) => Math.round((1 - taper.proportionOf(level)) * height);\n}\n\nexport function gridColumns(\n\t{ frequencyMax, frequencyMin }: Pick<FrequencyAxis, 'frequencyMax' | 'frequencyMin'>,\n\twidth: number,\n): Array<number> {\n\tconst taper = logTaper(frequencyMin, frequencyMax);\n\tconst columns: Array<number> = [];\n\tif (!taper) return columns;\n\n\tfor (\n\t\tlet exponent = Math.floor(Math.log10(frequencyMin)) + 1;\n\t\t10 ** exponent < frequencyMax;\n\t\texponent += 1\n\t) {\n\t\tcolumns.push(Math.round(taper.proportionOf(10 ** exponent) * width));\n\t}\n\n\treturn columns;\n}\n\nexport function gridRows(\n\t{ max, min }: LevelRange,\n\trowOf: (level: number) => number,\n): Array<number> {\n\tconst rows: Array<number> = [];\n\n\tfor (\n\t\tlet level = (Math.floor(min / gridDecibels) + 1) * gridDecibels;\n\t\tlevel < max;\n\t\tlevel += gridDecibels\n\t) {\n\t\trows.push(rowOf(level));\n\t}\n\n\treturn rows;\n}\n\nexport function zoneRows(\n\t{ clipFrom, hotFrom }: { clipFrom: number; hotFrom: number },\n\trowOf: (level: number) => number,\n): { clip: number; hot: number } {\n\tconst clip = rowOf(clipFrom);\n\n\treturn { clip, hot: Math.max(clip, rowOf(hotFrom)) };\n}\n\nexport function loudestBin(\n\tframe: ArrayLike<number>,\n\taxis: FrequencyAxis,\n): undefined | { decibels: number; frequency: number } {\n\tconst hertz = binHertz(axis);\n\tconst last = Math.min(Math.floor(axis.frequencyMax / hertz), frame.length - 1);\n\tlet loudest = -1;\n\tlet decibels = -Infinity;\n\n\tfor (let bin = Math.ceil(axis.frequencyMin / hertz); bin <= last; bin += 1) {\n\t\tconst level = frame[bin] ?? NaN;\n\n\t\tif (!(Number.isFinite(level) && level > decibels)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tloudest = bin;\n\t\tdecibels = level;\n\t}\n\n\treturn loudest === -1 ? undefined : { decibels, frequency: loudest * hertz };\n}\n"],"mappings":";;;AAcA,MAAM,eAAe;AAErB,SAAS,SAAS,EAAE,UAAU,cAAqC;CAClE,OAAO,aAAa,IAAI;AACzB;AAEA,SAAgB,YAAY,MAAqB,OAA6B;CAC7E,MAAM,QAAQ,IAAI,aAAa,QAAQ,CAAC;CACxC,MAAM,QAAQ,SAAS,KAAK,cAAc,KAAK,YAAY;CAC3D,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,QAAQ,SAAS,IAAI;CAE3B,KAAK,IAAI,OAAO,GAAG,QAAQ,OAAO,QAAQ,GAAG,MAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,IAAI;CAEzF,OAAO;AACR;AAEA,SAAgB,aACf,SACA,QACA,EAAE,OAAO,SACF;CACP,MAAM,WAAW,QAAyB,MAAM,OAAO,SAAU,OAAO,QAAQ,QAAS;CAEzF,KAAK,IAAI,SAAS,GAAG,SAAS,QAAQ,QAAQ,UAAU,GAAG;EAC1D,MAAM,OAAO,MAAM,WAAW;EAC9B,MAAM,KAAK,MAAM,SAAS,MAAM;EAChC,MAAM,QAAQ,KAAK,KAAK,IAAI;EAC5B,MAAM,OAAO,KAAK,KAAK,EAAE,IAAI;EAE7B,IAAI,QAAQ,MAAM;GACjB,MAAM,QAAQ,QAAQ,QAAQ,CAAC;GAE/B,QAAQ,UAAU,SAAS,QAAQ,KAAK,IAAI,WAAW,OAAO,MAAM,KAAK,QAAQ;GACjF;EACD;EAEA,IAAI,UAAU;EAEd,KAAK,IAAI,MAAM,OAAO,OAAO,QAAQ,MAAM,OAAO,QAAQ,OAAO,GAChE,UAAU,KAAK,IAAI,SAAS,QAAQ,GAAG,CAAC;EAEzC,QAAQ,UAAU;CACnB;AACD;AAEA,SAAgB,UAAU,EAAE,KAAK,OAAmB,QAA2C;CAC9F,MAAM,QAAQ,YAAY,KAAK,GAAG;CAElC,QAAQ,UAAU,KAAK,OAAO,IAAI,MAAM,aAAa,KAAK,KAAK,MAAM;AACtE;AAEA,SAAgB,YACf,EAAE,cAAc,gBAChB,OACgB;CAChB,MAAM,QAAQ,SAAS,cAAc,YAAY;CACjD,MAAM,UAAyB,CAAC;CAChC,IAAI,CAAC,OAAO,OAAO;CAEnB,KACC,IAAI,WAAW,KAAK,MAAM,KAAK,MAAM,YAAY,CAAC,IAAI,GACtD,MAAM,WAAW,cACjB,YAAY,GAEZ,QAAQ,KAAK,KAAK,MAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,KAAK,CAAC;CAGpE,OAAO;AACR;AAEA,SAAgB,SACf,EAAE,KAAK,OACP,OACgB;CAChB,MAAM,OAAsB,CAAC;CAE7B,KACC,IAAI,SAAS,KAAK,MAAM,MAAM,YAAY,IAAI,KAAK,cACnD,QAAQ,KACR,SAAS,cAET,KAAK,KAAK,MAAM,KAAK,CAAC;CAGvB,OAAO;AACR;AAEA,SAAgB,SACf,EAAE,UAAU,WACZ,OACgC;CAChC,MAAM,OAAO,MAAM,QAAQ;CAE3B,OAAO;EAAE;EAAM,KAAK,KAAK,IAAI,MAAM,MAAM,OAAO,CAAC;CAAE;AACpD;AAEA,SAAgB,WACf,OACA,MACsD;CACtD,MAAM,QAAQ,SAAS,IAAI;CAC3B,MAAM,OAAO,KAAK,IAAI,KAAK,MAAM,KAAK,eAAe,KAAK,GAAG,MAAM,SAAS,CAAC;CAC7E,IAAI,UAAU;CACd,IAAI,WAAW;CAEf,KAAK,IAAI,MAAM,KAAK,KAAK,KAAK,eAAe,KAAK,GAAG,OAAO,MAAM,OAAO,GAAG;EAC3E,MAAM,QAAQ,MAAM,QAAQ;EAE5B,IAAI,EAAE,OAAO,SAAS,KAAK,KAAK,QAAQ,WACvC;EAGD,UAAU;EACV,WAAW;CACZ;CAEA,OAAO,YAAY,KAAK,SAAY;EAAE;EAAU,WAAW,UAAU;CAAM;AAC5E"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ballisticsRates, fall } from "./ballistics.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/spectrum-levels.ts
|
|
4
|
+
const holdMs = 1500;
|
|
5
|
+
function createLevels(binCount) {
|
|
6
|
+
return {
|
|
7
|
+
bars: new Float64Array(binCount).fill(-Infinity),
|
|
8
|
+
heldAt: new Float64Array(binCount).fill(-Infinity),
|
|
9
|
+
holds: new Float64Array(binCount).fill(-Infinity),
|
|
10
|
+
incoming: new Float64Array(binCount).fill(-Infinity)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function receiveLevels({ incoming }, frame, floor) {
|
|
14
|
+
for (let bin = 0; bin < incoming.length; bin += 1) {
|
|
15
|
+
const level = frame[bin] ?? NaN;
|
|
16
|
+
incoming[bin] = Math.max(incoming[bin] ?? floor, Number.isFinite(level) ? level : floor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function fallenHold(hold, bar, { elapsedMs, isHoldStepped, pastHoldMs }) {
|
|
20
|
+
if (pastHoldMs < 0) return hold;
|
|
21
|
+
if (isHoldStepped) return bar;
|
|
22
|
+
return fall(hold, {
|
|
23
|
+
fallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond,
|
|
24
|
+
floor: bar
|
|
25
|
+
}, Math.min(elapsedMs, pastHoldMs));
|
|
26
|
+
}
|
|
27
|
+
function stepLevels(levels, step) {
|
|
28
|
+
const { bars, heldAt, holds, incoming } = levels;
|
|
29
|
+
const rate = {
|
|
30
|
+
fallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond,
|
|
31
|
+
floor: step.floor
|
|
32
|
+
};
|
|
33
|
+
for (let bin = 0; bin < bars.length; bin += 1) {
|
|
34
|
+
const bar = Math.max(fall(bars[bin] ?? step.floor, rate, step.elapsedMs), incoming[bin] ?? step.floor);
|
|
35
|
+
const hold = holds[bin] ?? bar;
|
|
36
|
+
bars[bin] = bar;
|
|
37
|
+
incoming[bin] = -Infinity;
|
|
38
|
+
if (!step.isHeld || bar >= hold) {
|
|
39
|
+
holds[bin] = bar;
|
|
40
|
+
heldAt[bin] = step.nowMs;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const pastHoldMs = step.nowMs - (heldAt[bin] ?? step.nowMs) - holdMs;
|
|
44
|
+
holds[bin] = fallenHold(hold, bar, {
|
|
45
|
+
...step,
|
|
46
|
+
pastHoldMs
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function areSettled({ holds }, floor) {
|
|
51
|
+
return holds.every((hold) => hold <= floor);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { areSettled, createLevels, receiveLevels, stepLevels };
|
|
56
|
+
//# sourceMappingURL=spectrum-levels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectrum-levels.js","names":[],"sources":["../../../src/lib/spectrum-levels.ts"],"sourcesContent":["import { ballisticsRates, fall } from '#lib/ballistics.ts';\n\nexport interface SpectrumLevels {\n\tbars: Float64Array;\n\theldAt: Float64Array;\n\tholds: Float64Array;\n\tincoming: Float64Array;\n}\n\ninterface LevelStep {\n\telapsedMs: number;\n\tfloor: number;\n\tisHeld: boolean;\n\tisHoldStepped: boolean;\n\tnowMs: number;\n}\n\nconst holdMs = 1500;\n\nexport function createLevels(binCount: number): SpectrumLevels {\n\treturn {\n\t\tbars: new Float64Array(binCount).fill(-Infinity),\n\t\theldAt: new Float64Array(binCount).fill(-Infinity),\n\t\tholds: new Float64Array(binCount).fill(-Infinity),\n\t\tincoming: new Float64Array(binCount).fill(-Infinity),\n\t};\n}\n\n// Collected apart from the bars, so a level received this frame is drawn before it falls\nexport function receiveLevels(\n\t{ incoming }: SpectrumLevels,\n\tframe: ArrayLike<number>,\n\tfloor: number,\n): void {\n\tfor (let bin = 0; bin < incoming.length; bin += 1) {\n\t\tconst level = frame[bin] ?? NaN;\n\n\t\tincoming[bin] = Math.max(incoming[bin] ?? floor, Number.isFinite(level) ? level : floor);\n\t}\n}\n\nfunction fallenHold(\n\thold: number,\n\tbar: number,\n\t{\n\t\telapsedMs,\n\t\tisHoldStepped,\n\t\tpastHoldMs,\n\t}: Pick<LevelStep, 'elapsedMs' | 'isHoldStepped'> & {\n\t\tpastHoldMs: number;\n\t},\n): number {\n\tif (pastHoldMs < 0) return hold;\n\tif (isHoldStepped) return bar;\n\n\treturn fall(\n\t\thold,\n\t\t{ fallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond, floor: bar },\n\t\tMath.min(elapsedMs, pastHoldMs),\n\t);\n}\n\nexport function stepLevels(levels: SpectrumLevels, step: LevelStep): void {\n\tconst { bars, heldAt, holds, incoming } = levels;\n\tconst rate = {\n\t\tfallDecibelsPerSecond: ballisticsRates.peak.fallDecibelsPerSecond,\n\t\tfloor: step.floor,\n\t};\n\n\tfor (let bin = 0; bin < bars.length; bin += 1) {\n\t\tconst bar = Math.max(\n\t\t\tfall(bars[bin] ?? step.floor, rate, step.elapsedMs),\n\t\t\tincoming[bin] ?? step.floor,\n\t\t);\n\t\tconst hold = holds[bin] ?? bar;\n\n\t\tbars[bin] = bar;\n\t\tincoming[bin] = -Infinity;\n\t\tif (!step.isHeld || bar >= hold) {\n\t\t\tholds[bin] = bar;\n\t\t\theldAt[bin] = step.nowMs;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst pastHoldMs = step.nowMs - (heldAt[bin] ?? step.nowMs) - holdMs;\n\n\t\tholds[bin] = fallenHold(hold, bar, { ...step, pastHoldMs });\n\t}\n}\n\n// A hold never sits under its bar, so the holds alone say whether anything is left to fall\nexport function areSettled({ holds }: SpectrumLevels, floor: number): boolean {\n\treturn holds.every((hold) => hold <= floor);\n}\n"],"mappings":";;;AAiBA,MAAM,SAAS;AAEf,SAAgB,aAAa,UAAkC;CAC9D,OAAO;EACN,MAAM,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,SAAS;EAC/C,QAAQ,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,SAAS;EACjD,OAAO,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,SAAS;EAChD,UAAU,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,SAAS;CACpD;AACD;AAGA,SAAgB,cACf,EAAE,YACF,OACA,OACO;CACP,KAAK,IAAI,MAAM,GAAG,MAAM,SAAS,QAAQ,OAAO,GAAG;EAClD,MAAM,QAAQ,MAAM,QAAQ;EAE5B,SAAS,OAAO,KAAK,IAAI,SAAS,QAAQ,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAK;CACxF;AACD;AAEA,SAAS,WACR,MACA,KACA,EACC,WACA,eACA,cAIQ;CACT,IAAI,aAAa,GAAG,OAAO;CAC3B,IAAI,eAAe,OAAO;CAE1B,OAAO,KACN,MACA;EAAE,uBAAuB,gBAAgB,KAAK;EAAuB,OAAO;CAAI,GAChF,KAAK,IAAI,WAAW,UAAU,CAC/B;AACD;AAEA,SAAgB,WAAW,QAAwB,MAAuB;CACzE,MAAM,EAAE,MAAM,QAAQ,OAAO,aAAa;CAC1C,MAAM,OAAO;EACZ,uBAAuB,gBAAgB,KAAK;EAC5C,OAAO,KAAK;CACb;CAEA,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK,QAAQ,OAAO,GAAG;EAC9C,MAAM,MAAM,KAAK,IAChB,KAAK,KAAK,QAAQ,KAAK,OAAO,MAAM,KAAK,SAAS,GAClD,SAAS,QAAQ,KAAK,KACvB;EACA,MAAM,OAAO,MAAM,QAAQ;EAE3B,KAAK,OAAO;EACZ,SAAS,OAAO;EAChB,IAAI,CAAC,KAAK,UAAU,OAAO,MAAM;GAChC,MAAM,OAAO;GACb,OAAO,OAAO,KAAK;GACnB;EACD;EAEA,MAAM,aAAa,KAAK,SAAS,OAAO,QAAQ,KAAK,SAAS;EAE9D,MAAM,OAAO,WAAW,MAAM,KAAK;GAAE,GAAG;GAAM;EAAW,CAAC;CAC3D;AACD;AAGA,SAAgB,WAAW,EAAE,SAAyB,OAAwB;CAC7E,OAAO,MAAM,OAAO,SAAS,QAAQ,KAAK;AAC3C"}
|
|
@@ -18,6 +18,17 @@ function stripBars(peaks, size, grid) {
|
|
|
18
18
|
};
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
function stripGroove(size, ratio) {
|
|
22
|
+
if (ratio <= 0) return void 0;
|
|
23
|
+
const height = Math.max(1, Math.round(size.height * ratio));
|
|
24
|
+
return {
|
|
25
|
+
height,
|
|
26
|
+
radius: height / 2,
|
|
27
|
+
width: size.width,
|
|
28
|
+
x: 0,
|
|
29
|
+
y: Math.round((size.height - height) / 2)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
21
32
|
function stripRegions(state, widthPx) {
|
|
22
33
|
const px = (proportion) => Math.round((Number.isFinite(proportion) ? proportion : 0) * widthPx);
|
|
23
34
|
const buffered = state.buffered.map(([start, end]) => [px(start), px(end)]).filter(([start, end]) => end > start);
|
|
@@ -45,5 +56,5 @@ function stripRegions(state, widthPx) {
|
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
//#endregion
|
|
48
|
-
export { stripBars, stripRegions };
|
|
59
|
+
export { stripBars, stripGroove, stripRegions };
|
|
49
60
|
//# sourceMappingURL=strip-scene.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strip-scene.js","names":[],"sources":["../../../src/lib/strip-scene.ts"],"sourcesContent":["import type { SurfaceSize } from '#lib/canvas-surface.ts';\n\nimport { clampProportion } from '#lib/math.ts';\nimport { resamplePeaks } from '#lib/resample-peaks.ts';\n\ninterface BarRect {\n\theight: number;\n\tradius: number;\n\twidth: number;\n\tx: number;\n\ty: number;\n}\n\ninterface BarGrid {\n\tgapRatio: number;\n\tpitch: number;\n\tradiusRatio: number;\n}\n\ninterface RegionState {\n\tbuffered: ReadonlyArray<[number, number]>;\n\tplayed: number;\n\tscrub?: number;\n}\n\nexport interface StripRegions {\n\tkey: string;\n\tregions: Array<{ from: number; kind: 'buffered' | 'played' | 'scrub'; to: number }>;\n}\n\n// Whole device pixels, or bars alias unevenly and Skia's CPU raster crawls\nexport function stripBars(\n\tpeaks: ArrayLike<number>,\n\tsize: SurfaceSize,\n\tgrid: BarGrid,\n): Array<BarRect> {\n\tconst pitch = Math.max(1, Math.round(grid.pitch * size.dpr));\n\tconst gap = Math.min(pitch - 1, Math.max(0, Math.round(pitch * grid.gapRatio)));\n\tconst width = pitch - gap;\n\tconst count = Math.max(0, Math.floor((size.width + gap) / pitch));\n\n\treturn resamplePeaks(peaks, count).map((peak, index) => {\n\t\tconst height = Math.max(1, Math.round(clampProportion(peak) * size.height));\n\n\t\treturn {\n\t\t\theight,\n\t\t\tradius: width * grid.radiusRatio,\n\t\t\twidth,\n\t\t\tx: index * pitch,\n\t\t\ty: Math.round((size.height - height) / 2),\n\t\t};\n\t});\n}\n\nexport function stripRegions(state: RegionState, widthPx: number): StripRegions {\n\tconst px = (proportion: number): number =>\n\t\tMath.round((Number.isFinite(proportion) ? proportion : 0) * widthPx);\n\tconst buffered = state.buffered\n\t\t.map(([start, end]): [number, number] => [px(start), px(end)])\n\t\t.filter(([start, end]) => end > start);\n\tconst played = px(state.played);\n\tconst scrub = state.scrub === undefined ? played : px(state.scrub);\n\tconst regions: StripRegions['regions'] = buffered.map(([from, to]) => ({\n\t\tfrom,\n\t\tkind: 'buffered',\n\t\tto,\n\t}));\n\n\tif (played > 0) regions.push({ from: 0, kind: 'played', to: played });\n\tif (scrub !== played) {\n\t\tregions.push({ from: Math.min(played, scrub), kind: 'scrub', to: Math.max(played, scrub) });\n\t}\n\n\treturn { key: `${String(played)}:${String(scrub)}:${buffered.join(',')}`, regions };\n}\n"],"mappings":";;;;AA+BA,SAAgB,UACf,OACA,MACA,MACiB;CACjB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,QAAQ,KAAK,GAAG,CAAC;CAC3D,MAAM,MAAM,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC;CAC9E,MAAM,QAAQ,QAAQ;CACtB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,OAAO,KAAK,CAAC;CAEhE,OAAO,cAAc,OAAO,KAAK,CAAC,CAAC,KAAK,MAAM,UAAU;EACvD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,IAAI,IAAI,KAAK,MAAM,CAAC;EAE1E,OAAO;GACN;GACA,QAAQ,QAAQ,KAAK;GACrB;GACA,GAAG,QAAQ;GACX,GAAG,KAAK,OAAO,KAAK,SAAS,UAAU,CAAC;EACzC;CACD,CAAC;AACF;AAEA,SAAgB,aAAa,OAAoB,SAA+B;CAC/E,MAAM,MAAM,eACX,KAAK,OAAO,OAAO,SAAS,UAAU,IAAI,aAAa,KAAK,OAAO;CACpE,MAAM,WAAW,MAAM,SACrB,KAAK,CAAC,OAAO,SAA2B,CAAC,GAAG,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAC7D,QAAQ,CAAC,OAAO,SAAS,MAAM,KAAK;CACtC,MAAM,SAAS,GAAG,MAAM,MAAM;CAC9B,MAAM,QAAQ,MAAM,UAAU,SAAY,SAAS,GAAG,MAAM,KAAK;CACjE,MAAM,UAAmC,SAAS,KAAK,CAAC,MAAM,SAAS;EACtE;EACA,MAAM;EACN;CACD,EAAE;CAEF,IAAI,SAAS,GAAG,QAAQ,KAAK;EAAE,MAAM;EAAG,MAAM;EAAU,IAAI;CAAO,CAAC;CACpE,IAAI,UAAU,QACb,QAAQ,KAAK;EAAE,MAAM,KAAK,IAAI,QAAQ,KAAK;EAAG,MAAM;EAAS,IAAI,KAAK,IAAI,QAAQ,KAAK;CAAE,CAAC;CAG3F,OAAO;EAAE,KAAK,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG;EAAK;CAAQ;AACnF"}
|
|
1
|
+
{"version":3,"file":"strip-scene.js","names":[],"sources":["../../../src/lib/strip-scene.ts"],"sourcesContent":["import type { SurfaceSize } from '#lib/canvas-surface.ts';\n\nimport { clampProportion } from '#lib/math.ts';\nimport { resamplePeaks } from '#lib/resample-peaks.ts';\n\ninterface BarRect {\n\theight: number;\n\tradius: number;\n\twidth: number;\n\tx: number;\n\ty: number;\n}\n\ninterface BarGrid {\n\tgapRatio: number;\n\tpitch: number;\n\tradiusRatio: number;\n}\n\ninterface RegionState {\n\tbuffered: ReadonlyArray<[number, number]>;\n\tplayed: number;\n\tscrub?: number;\n}\n\nexport interface StripRegions {\n\tkey: string;\n\tregions: Array<{ from: number; kind: 'buffered' | 'played' | 'scrub'; to: number }>;\n}\n\n// Whole device pixels, or bars alias unevenly and Skia's CPU raster crawls\nexport function stripBars(\n\tpeaks: ArrayLike<number>,\n\tsize: SurfaceSize,\n\tgrid: BarGrid,\n): Array<BarRect> {\n\tconst pitch = Math.max(1, Math.round(grid.pitch * size.dpr));\n\tconst gap = Math.min(pitch - 1, Math.max(0, Math.round(pitch * grid.gapRatio)));\n\tconst width = pitch - gap;\n\tconst count = Math.max(0, Math.floor((size.width + gap) / pitch));\n\n\treturn resamplePeaks(peaks, count).map((peak, index) => {\n\t\tconst height = Math.max(1, Math.round(clampProportion(peak) * size.height));\n\n\t\treturn {\n\t\t\theight,\n\t\t\tradius: width * grid.radiusRatio,\n\t\t\twidth,\n\t\t\tx: index * pitch,\n\t\t\ty: Math.round((size.height - height) / 2),\n\t\t};\n\t});\n}\n\nexport function stripGroove(size: SurfaceSize, ratio: number): BarRect | undefined {\n\tif (ratio <= 0) return undefined;\n\n\tconst height = Math.max(1, Math.round(size.height * ratio));\n\n\treturn {\n\t\theight,\n\t\tradius: height / 2,\n\t\twidth: size.width,\n\t\tx: 0,\n\t\ty: Math.round((size.height - height) / 2),\n\t};\n}\n\nexport function stripRegions(state: RegionState, widthPx: number): StripRegions {\n\tconst px = (proportion: number): number =>\n\t\tMath.round((Number.isFinite(proportion) ? proportion : 0) * widthPx);\n\tconst buffered = state.buffered\n\t\t.map(([start, end]): [number, number] => [px(start), px(end)])\n\t\t.filter(([start, end]) => end > start);\n\tconst played = px(state.played);\n\tconst scrub = state.scrub === undefined ? played : px(state.scrub);\n\tconst regions: StripRegions['regions'] = buffered.map(([from, to]) => ({\n\t\tfrom,\n\t\tkind: 'buffered',\n\t\tto,\n\t}));\n\n\tif (played > 0) regions.push({ from: 0, kind: 'played', to: played });\n\tif (scrub !== played) {\n\t\tregions.push({ from: Math.min(played, scrub), kind: 'scrub', to: Math.max(played, scrub) });\n\t}\n\n\treturn { key: `${String(played)}:${String(scrub)}:${buffered.join(',')}`, regions };\n}\n"],"mappings":";;;;AA+BA,SAAgB,UACf,OACA,MACA,MACiB;CACjB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,QAAQ,KAAK,GAAG,CAAC;CAC3D,MAAM,MAAM,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC;CAC9E,MAAM,QAAQ,QAAQ;CACtB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,OAAO,KAAK,CAAC;CAEhE,OAAO,cAAc,OAAO,KAAK,CAAC,CAAC,KAAK,MAAM,UAAU;EACvD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,IAAI,IAAI,KAAK,MAAM,CAAC;EAE1E,OAAO;GACN;GACA,QAAQ,QAAQ,KAAK;GACrB;GACA,GAAG,QAAQ;GACX,GAAG,KAAK,OAAO,KAAK,SAAS,UAAU,CAAC;EACzC;CACD,CAAC;AACF;AAEA,SAAgB,YAAY,MAAmB,OAAoC;CAClF,IAAI,SAAS,GAAG,OAAO;CAEvB,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,SAAS,KAAK,CAAC;CAE1D,OAAO;EACN;EACA,QAAQ,SAAS;EACjB,OAAO,KAAK;EACZ,GAAG;EACH,GAAG,KAAK,OAAO,KAAK,SAAS,UAAU,CAAC;CACzC;AACD;AAEA,SAAgB,aAAa,OAAoB,SAA+B;CAC/E,MAAM,MAAM,eACX,KAAK,OAAO,OAAO,SAAS,UAAU,IAAI,aAAa,KAAK,OAAO;CACpE,MAAM,WAAW,MAAM,SACrB,KAAK,CAAC,OAAO,SAA2B,CAAC,GAAG,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAC7D,QAAQ,CAAC,OAAO,SAAS,MAAM,KAAK;CACtC,MAAM,SAAS,GAAG,MAAM,MAAM;CAC9B,MAAM,QAAQ,MAAM,UAAU,SAAY,SAAS,GAAG,MAAM,KAAK;CACjE,MAAM,UAAmC,SAAS,KAAK,CAAC,MAAM,SAAS;EACtE;EACA,MAAM;EACN;CACD,EAAE;CAEF,IAAI,SAAS,GAAG,QAAQ,KAAK;EAAE,MAAM;EAAG,MAAM;EAAU,IAAI;CAAO,CAAC;CACpE,IAAI,UAAU,QACb,QAAQ,KAAK;EAAE,MAAM,KAAK,IAAI,QAAQ,KAAK;EAAG,MAAM;EAAS,IAAI,KAAK,IAAI,QAAQ,KAAK;CAAE,CAAC;CAG3F,OAAO;EAAE,KAAK,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,SAAS,KAAK,GAAG;EAAK;CAAQ;AACnF"}
|
|
@@ -3,7 +3,14 @@ function readRegions(regions) {
|
|
|
3
3
|
if ("start" in regions) return Array.from({ length: regions.length }, (_entry, index) => [regions.start(index), regions.end(index)]);
|
|
4
4
|
return [...regions].map(([start, end]) => [start, end]);
|
|
5
5
|
}
|
|
6
|
+
function dueRegions(regions, listedMs, [nowMs, delayMs]) {
|
|
7
|
+
if (delayMs <= 0) return [...regions];
|
|
8
|
+
return regions.filter((region) => nowMs - (listedMs.get(region.join(":")) ?? nowMs) >= delayMs);
|
|
9
|
+
}
|
|
10
|
+
function sortedRegions(regions) {
|
|
11
|
+
return regions ? readRegions(regions).toSorted((first, second) => first[0] - second[0]) : [];
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
//#endregion
|
|
8
|
-
export { readRegions };
|
|
15
|
+
export { dueRegions, readRegions, sortedRegions };
|
|
9
16
|
//# sourceMappingURL=time-regions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-regions.js","names":[],"sources":["../../../src/lib/time-regions.ts"],"sourcesContent":["export type TimeRegions = Iterable<readonly [number, number]> | TimeRanges;\n\nexport function readRegions(regions: TimeRegions): Array<[number, number]> {\n\tif ('start' in regions) {\n\t\treturn Array.from({ length: regions.length }, (_entry, index) => [\n\t\t\tregions.start(index),\n\t\t\tregions.end(index),\n\t\t]);\n\t}\n\n\treturn [...regions].map(([start, end]) => [start, end]);\n}\n"],"mappings":";AAEA,SAAgB,YAAY,SAA+C;CAC1E,IAAI,WAAW,SACd,OAAO,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,IAAI,QAAQ,UAAU,CAChE,QAAQ,MAAM,KAAK,GACnB,QAAQ,IAAI,KAAK,CAClB,CAAC;CAGF,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,OAAO,GAAG,CAAC;AACvD"}
|
|
1
|
+
{"version":3,"file":"time-regions.js","names":[],"sources":["../../../src/lib/time-regions.ts"],"sourcesContent":["export type TimeRegions = Iterable<readonly [number, number]> | TimeRanges;\n\nexport function readRegions(regions: TimeRegions): Array<[number, number]> {\n\tif ('start' in regions) {\n\t\treturn Array.from({ length: regions.length }, (_entry, index) => [\n\t\t\tregions.start(index),\n\t\t\tregions.end(index),\n\t\t]);\n\t}\n\n\treturn [...regions].map(([start, end]) => [start, end]);\n}\n\nexport function dueRegions(\n\tregions: ReadonlyArray<[number, number]>,\n\tlistedMs: ReadonlyMap<string, number>,\n\t[nowMs, delayMs]: [number, number],\n): Array<[number, number]> {\n\tif (delayMs <= 0) return [...regions];\n\n\treturn regions.filter((region) => nowMs - (listedMs.get(region.join(':')) ?? nowMs) >= delayMs);\n}\n\nexport function sortedRegions(regions: TimeRegions | undefined): Array<[number, number]> {\n\treturn regions ? readRegions(regions).toSorted((first, second) => first[0] - second[0]) : [];\n}\n"],"mappings":";AAEA,SAAgB,YAAY,SAA+C;CAC1E,IAAI,WAAW,SACd,OAAO,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,IAAI,QAAQ,UAAU,CAChE,QAAQ,MAAM,KAAK,GACnB,QAAQ,IAAI,KAAK,CAClB,CAAC;CAGF,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,OAAO,GAAG,CAAC;AACvD;AAEA,SAAgB,WACf,SACA,UACA,CAAC,OAAO,UACkB;CAC1B,IAAI,WAAW,GAAG,OAAO,CAAC,GAAG,OAAO;CAEpC,OAAO,QAAQ,QAAQ,WAAW,SAAS,SAAS,IAAI,OAAO,KAAK,GAAG,CAAC,KAAK,UAAU,OAAO;AAC/F;AAEA,SAAgB,cAAc,SAA2D;CACxF,OAAO,UAAU,YAAY,OAAO,CAAC,CAAC,UAAU,OAAO,WAAW,MAAM,KAAK,OAAO,EAAE,IAAI,CAAC;AAC5F"}
|
package/dist/skins/flat.css
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
--sonic-cap-edge: oklch(72% 0.01 255deg);
|
|
9
9
|
--sonic-well: var(--sonic-unlit);
|
|
10
10
|
--sonic-well-lit: var(--sonic-lit);
|
|
11
|
-
--sonic-bezel: oklch(
|
|
11
|
+
--sonic-bezel: oklch(96% 0.003 255deg);
|
|
12
12
|
--sonic-lens: var(--sonic-unlit);
|
|
13
13
|
--sonic-led-lens-ratio: 0.8;
|
|
14
14
|
--sonic-waveform-edge: var(--sonic-lit);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
--sonic-glass-edge: oklch(84% 0.006 255deg);
|
|
20
20
|
--sonic-glass-texture: 0;
|
|
21
21
|
--sonic-panel: oklch(98.5% 0.002 255deg);
|
|
22
|
+
--sonic-panel-text: oklch(27% 0.012 255deg);
|
|
22
23
|
--sonic-button-depth-ratio: 0;
|
|
23
24
|
--sonic-segmented-depth-ratio: 0;
|
|
24
25
|
}
|
package/dist/styles/button.css
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
/* Before the latched rule too; a button with an LED blinks the LED and keeps its legend */
|
|
82
82
|
:where([busy]) > .sonic-button:not(:disabled, [aria-disabled='true'], :has(.sonic-led)) {
|
|
83
83
|
color: color-mix(in oklab, var(--_sonic-lit) 55%, var(--_sonic-ink));
|
|
84
|
+
transition-delay: var(--sonic-button-busy-delay, 0s);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
.sonic-button:active:where(:not(:disabled, [aria-disabled='true'])),
|
|
@@ -152,6 +153,27 @@
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
.sonic-button:not(:has(> .sonic-button-cap > :not(.sonic-led))) {
|
|
157
|
+
/* Invalid with both tokens unset, which is the one case that draws */
|
|
158
|
+
--_sonic-blank-edge-off: color-mix(
|
|
159
|
+
in oklab,
|
|
160
|
+
var(--sonic-cap-edge, var(--sonic-cap)) 0%,
|
|
161
|
+
transparent
|
|
162
|
+
);
|
|
163
|
+
--_sonic-blank-edge: var(
|
|
164
|
+
--_sonic-blank-edge-off,
|
|
165
|
+
color-mix(in oklab, currentcolor 35%, transparent)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
&:is([aria-pressed='true'], :where([momentary]:state(pressed)) > *) {
|
|
169
|
+
--_sonic-blank-edge: var(--_sonic-blank-edge-off, currentcolor);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@media (forced-colors: active) {
|
|
173
|
+
--_sonic-blank-edge: transparent;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
155
177
|
.sonic-button-cap {
|
|
156
178
|
--_sonic-keycap-corner: max(0px, var(--_sonic-button-corner) - var(--_sonic-button-gap));
|
|
157
179
|
--_sonic-keycap-bevel: calc(var(--_sonic-button-size) * var(--_sonic-button-bevel-ratio));
|
|
@@ -205,12 +227,17 @@
|
|
|
205
227
|
display: none;
|
|
206
228
|
}
|
|
207
229
|
|
|
230
|
+
.sonic-button-cap > [data-sonic-hidden] {
|
|
231
|
+
display: none;
|
|
232
|
+
}
|
|
233
|
+
|
|
208
234
|
[busy]
|
|
209
235
|
> .sonic-button:not(:disabled, [aria-disabled='true'], :has(.sonic-led))
|
|
210
236
|
> .sonic-button-cap
|
|
211
237
|
> *,
|
|
212
238
|
[busy] > .sonic-button:not(:disabled, [aria-disabled='true']) .sonic-led::after {
|
|
213
|
-
animation: sonic-button-busy 500ms ease-in-out infinite
|
|
239
|
+
animation: sonic-button-busy 500ms ease-in-out var(--sonic-button-busy-delay, 0s) infinite
|
|
240
|
+
alternate;
|
|
214
241
|
|
|
215
242
|
@media (prefers-reduced-motion: reduce) {
|
|
216
243
|
animation: none;
|