@xsynaptic/sonic-ui 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -21
- package/dist/default/dat.js +50 -0
- package/dist/default/define/button.js +7 -0
- package/dist/default/define/envelope.js +7 -0
- package/dist/default/define/split.js +7 -0
- package/dist/default/define/switch.js +7 -0
- package/dist/default/define/waveform.js +7 -0
- package/dist/default/define/wavestrip.js +7 -0
- package/dist/default/define/xy.js +7 -0
- package/dist/default/define.js +7 -1
- package/dist/default/elements/button.js +165 -0
- package/dist/default/elements/dial.js +19 -23
- package/dist/default/elements/envelope.js +367 -0
- package/dist/default/elements/meter.js +168 -26
- package/dist/default/elements/number.js +21 -8
- package/dist/default/elements/radio-group.js +185 -0
- package/dist/default/elements/readout-claim.js +81 -0
- package/dist/default/elements/readout.js +35 -0
- package/dist/default/elements/segmented.js +9 -159
- package/dist/default/elements/slider.js +64 -27
- package/dist/default/elements/sonic-element.js +24 -13
- package/dist/default/elements/split.js +249 -0
- package/dist/default/elements/switch.js +279 -0
- package/dist/default/elements/value-element.js +691 -0
- package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
- package/dist/default/elements/wave-element.js +77 -0
- package/dist/default/elements/waveform.js +305 -0
- package/dist/default/elements/wavestrip.js +182 -0
- package/dist/default/elements/xy.js +473 -0
- package/dist/default/index.js +10 -2
- package/dist/default/lib/adsr-shape.js +108 -0
- package/dist/default/lib/ballistics.js +55 -0
- package/dist/default/lib/canvas-surface.js +192 -0
- package/dist/default/lib/copy-node.js +32 -2
- package/dist/default/lib/crossfade.js +22 -0
- package/dist/default/lib/distribute.js +215 -0
- package/dist/default/lib/drag-step.js +99 -0
- package/dist/default/lib/envelope-curve.js +9 -0
- package/dist/default/lib/field.js +94 -0
- package/dist/default/lib/focus-by-pointer.js +28 -0
- package/dist/default/lib/frame-timeline.js +62 -0
- package/dist/default/lib/hold.js +38 -0
- package/dist/default/lib/marker-band.js +36 -0
- package/dist/default/lib/marker-rider.js +114 -0
- package/dist/default/lib/math.js +25 -0
- package/dist/default/lib/modifier-press.js +11 -0
- package/dist/default/lib/number-list.js +14 -0
- package/dist/default/lib/pointer-drag.js +43 -0
- package/dist/default/lib/render.js +20 -5
- package/dist/default/lib/resample-peaks.js +36 -0
- package/dist/default/lib/strip-scene.js +48 -0
- package/dist/default/lib/taper.js +32 -16
- package/dist/default/lib/time-regions.js +8 -0
- package/dist/default/lib/tracking-clock.js +35 -0
- package/dist/default/lib/value-mapping.js +115 -0
- package/dist/default/lib/waveform-buckets.js +41 -0
- package/dist/dev/dat.d.ts +19 -0
- package/dist/dev/dat.js +51 -0
- package/dist/dev/dat.js.map +1 -0
- package/dist/dev/define/button.d.ts +2 -0
- package/dist/dev/define/button.js +8 -0
- package/dist/dev/define/button.js.map +1 -0
- package/dist/dev/define/dial.js.map +1 -1
- package/dist/dev/define/envelope.d.ts +2 -0
- package/dist/dev/define/envelope.js +8 -0
- package/dist/dev/define/envelope.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -1
- package/dist/dev/define/number.js.map +1 -1
- package/dist/dev/define/segmented.js.map +1 -1
- package/dist/dev/define/slider.js.map +1 -1
- package/dist/dev/define/split.d.ts +2 -0
- package/dist/dev/define/split.js +8 -0
- package/dist/dev/define/split.js.map +1 -0
- package/dist/dev/define/switch.d.ts +2 -0
- package/dist/dev/define/switch.js +8 -0
- package/dist/dev/define/switch.js.map +1 -0
- package/dist/dev/define/waveform.d.ts +2 -0
- package/dist/dev/define/waveform.js +8 -0
- package/dist/dev/define/waveform.js.map +1 -0
- package/dist/dev/define/wavestrip.d.ts +2 -0
- package/dist/dev/define/wavestrip.js +8 -0
- package/dist/dev/define/wavestrip.js.map +1 -0
- package/dist/dev/define/xy.d.ts +2 -0
- package/dist/dev/define/xy.js +8 -0
- package/dist/dev/define/xy.js.map +1 -0
- package/dist/dev/define.d.ts +15 -3
- package/dist/dev/define.js +7 -1
- package/dist/dev/elements/{key.d.ts → button.d.ts} +12 -6
- package/dist/dev/elements/button.js +166 -0
- package/dist/dev/elements/button.js.map +1 -0
- package/dist/dev/elements/dial.d.ts +6 -4
- package/dist/dev/elements/dial.js +19 -23
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +36 -0
- package/dist/dev/elements/envelope.js +372 -0
- package/dist/dev/elements/envelope.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +1 -1
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +12 -1
- package/dist/dev/elements/meter.js +168 -26
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +7 -3
- package/dist/dev/elements/number.js +21 -8
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +25 -0
- package/dist/dev/elements/radio-group.js +186 -0
- package/dist/dev/elements/radio-group.js.map +1 -0
- package/dist/dev/elements/readout-claim.js +82 -0
- package/dist/dev/elements/readout-claim.js.map +1 -0
- package/dist/dev/elements/readout.js +36 -0
- package/dist/dev/elements/readout.js.map +1 -0
- package/dist/dev/elements/segmented.d.ts +4 -10
- package/dist/dev/elements/segmented.js +9 -159
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +9 -3
- package/dist/dev/elements/slider.js +64 -27
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +4 -3
- package/dist/dev/elements/sonic-element.js +51 -22
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.d.ts +21 -0
- package/dist/dev/elements/split.js +258 -0
- package/dist/dev/elements/split.js.map +1 -0
- package/dist/dev/elements/switch.d.ts +35 -0
- package/dist/dev/elements/switch.js +280 -0
- package/dist/dev/elements/switch.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +84 -0
- package/dist/dev/elements/value-element.js +692 -0
- package/dist/dev/elements/value-element.js.map +1 -0
- package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
- package/dist/dev/elements/value-entry.js.map +1 -0
- package/dist/dev/elements/wave-element.d.ts +36 -0
- package/dist/dev/elements/wave-element.js +78 -0
- package/dist/dev/elements/wave-element.js.map +1 -0
- package/dist/dev/elements/waveform.d.ts +55 -0
- package/dist/dev/elements/waveform.js +306 -0
- package/dist/dev/elements/waveform.js.map +1 -0
- package/dist/dev/elements/wavestrip.d.ts +51 -0
- package/dist/dev/elements/wavestrip.js +183 -0
- package/dist/dev/elements/wavestrip.js.map +1 -0
- package/dist/dev/elements/xy.d.ts +62 -0
- package/dist/dev/elements/xy.js +474 -0
- package/dist/dev/elements/xy.js.map +1 -0
- package/dist/dev/index.d.ts +11 -2
- package/dist/dev/index.js +10 -2
- package/dist/dev/lib/adsr-shape.js +109 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -0
- package/dist/dev/lib/ballistics.js +56 -0
- package/dist/dev/lib/ballistics.js.map +1 -0
- package/dist/dev/lib/canvas-surface.d.ts +25 -0
- package/dist/dev/lib/canvas-surface.js +193 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -0
- package/dist/dev/lib/copy-node.js +32 -2
- package/dist/dev/lib/copy-node.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +12 -0
- package/dist/dev/lib/crossfade.js +23 -0
- package/dist/dev/lib/crossfade.js.map +1 -0
- package/dist/dev/lib/distribute.d.ts +4 -0
- package/dist/dev/lib/distribute.js +216 -0
- package/dist/dev/lib/distribute.js.map +1 -0
- package/dist/dev/lib/drag-step.js +100 -0
- package/dist/dev/lib/drag-step.js.map +1 -0
- package/dist/dev/lib/envelope-curve.d.ts +4 -0
- package/dist/dev/lib/envelope-curve.js +10 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -0
- package/dist/dev/lib/field.d.ts +4 -0
- package/dist/dev/lib/field.js +95 -0
- package/dist/dev/lib/field.js.map +1 -0
- package/dist/dev/lib/focus-by-pointer.js +29 -0
- package/dist/dev/lib/focus-by-pointer.js.map +1 -0
- package/dist/dev/lib/frame-timeline.js +63 -0
- package/dist/dev/lib/frame-timeline.js.map +1 -0
- package/dist/dev/lib/hold.js +39 -0
- package/dist/dev/lib/hold.js.map +1 -0
- package/dist/dev/lib/marker-band.js +37 -0
- package/dist/dev/lib/marker-band.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +115 -0
- package/dist/dev/lib/marker-rider.js.map +1 -0
- package/dist/dev/lib/math.js +26 -0
- package/dist/dev/lib/math.js.map +1 -0
- package/dist/dev/lib/modifier-press.js +12 -0
- package/dist/dev/lib/modifier-press.js.map +1 -0
- package/dist/dev/lib/number-list.js +15 -0
- package/dist/dev/lib/number-list.js.map +1 -0
- package/dist/dev/lib/pointer-drag.js +44 -0
- package/dist/dev/lib/pointer-drag.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/render.js +20 -5
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +37 -0
- package/dist/dev/lib/resample-peaks.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +49 -0
- package/dist/dev/lib/strip-scene.js.map +1 -0
- package/dist/dev/lib/taper.js +32 -16
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/dev/lib/time-regions.d.ts +4 -0
- package/dist/dev/lib/time-regions.js +9 -0
- package/dist/dev/lib/time-regions.js.map +1 -0
- package/dist/dev/lib/tracking-clock.js +36 -0
- package/dist/dev/lib/tracking-clock.js.map +1 -0
- package/dist/dev/lib/value-mapping.d.ts +23 -0
- package/dist/dev/lib/value-mapping.js +116 -0
- package/dist/dev/lib/value-mapping.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +9 -0
- package/dist/dev/lib/waveform-buckets.js +42 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -0
- package/dist/skins/ivory.css +1 -2
- package/dist/styles/button.css +212 -0
- package/dist/styles/controls.css +8 -1
- package/dist/styles/dial.css +130 -44
- package/dist/styles/envelope.css +159 -0
- package/dist/styles/led.css +44 -6
- package/dist/styles/material.css +321 -71
- package/dist/styles/meter.css +109 -28
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +59 -0
- package/dist/styles/ring.css +74 -0
- package/dist/styles/segmented.css +22 -54
- package/dist/styles/slider.css +256 -58
- package/dist/styles/switch.css +347 -0
- package/dist/styles/waveform.css +173 -0
- package/dist/styles/wavestrip.css +215 -0
- package/dist/styles/xy.css +204 -0
- package/package.json +14 -2
- package/dist/default/define/key.js +0 -7
- package/dist/default/elements/key.js +0 -144
- package/dist/default/elements/range-element.js +0 -433
- package/dist/dev/define/key.d.ts +0 -2
- package/dist/dev/define/key.js +0 -8
- package/dist/dev/define/key.js.map +0 -1
- package/dist/dev/elements/key.js +0 -145
- package/dist/dev/elements/key.js.map +0 -1
- package/dist/dev/elements/range-element.d.ts +0 -56
- package/dist/dev/elements/range-element.js +0 -434
- package/dist/dev/elements/range-element.js.map +0 -1
- package/dist/dev/elements/range-entry.js.map +0 -1
- package/dist/styles/key.css +0 -197
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/lib/math.ts
|
|
2
|
+
function clamp(value, min, max) {
|
|
3
|
+
return Math.min(max, Math.max(min, value));
|
|
4
|
+
}
|
|
5
|
+
function clampProportion(fraction) {
|
|
6
|
+
return clamp(fraction, 0, 1);
|
|
7
|
+
}
|
|
8
|
+
function wrapProportion(fraction) {
|
|
9
|
+
return fraction - Math.floor(fraction);
|
|
10
|
+
}
|
|
11
|
+
function trimFloat(value) {
|
|
12
|
+
return Number(value.toPrecision(12));
|
|
13
|
+
}
|
|
14
|
+
function decimalPlaces(value) {
|
|
15
|
+
const [digits = "", exponent = "0"] = String(value).split("e", 2);
|
|
16
|
+
const fraction = digits.split(".", 2)[1] ?? "";
|
|
17
|
+
return Math.max(0, fraction.length - Number(exponent));
|
|
18
|
+
}
|
|
19
|
+
function roundTo(value, places) {
|
|
20
|
+
const rounded = Number(value.toFixed(Math.min(100, places)));
|
|
21
|
+
return rounded === 0 ? 0 : rounded;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { clamp, clampProportion, decimalPlaces, roundTo, trimFloat, wrapProportion };
|
|
26
|
+
//# sourceMappingURL=math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"math.js","names":[],"sources":["../../../src/lib/math.ts"],"sourcesContent":["export function clamp(value: number, min: number, max: number): number {\n\treturn Math.min(max, Math.max(min, value));\n}\n\nexport function clampProportion(fraction: number): number {\n\treturn clamp(fraction, 0, 1);\n}\n\nexport function wrapProportion(fraction: number): number {\n\treturn fraction - Math.floor(fraction);\n}\n\nexport function trimFloat(value: number): number {\n\treturn Number(value.toPrecision(12));\n}\n\n// `String(1e-7)` is \"1e-7\", so the exponent counts as places too\nexport function decimalPlaces(value: number): number {\n\tconst [digits = '', exponent = '0'] = String(value).split('e', 2);\n\tconst fraction = digits.split('.', 2)[1] ?? '';\n\n\treturn Math.max(0, fraction.length - Number(exponent));\n}\n\n// `toFixed` takes at most 100 places, and turns a residue below zero into -0\nexport function roundTo(value: number, places: number): number {\n\tconst rounded = Number(value.toFixed(Math.min(100, places)));\n\n\treturn rounded === 0 ? 0 : rounded;\n}\n"],"mappings":";AAAA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACtE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC1C;AAEA,SAAgB,gBAAgB,UAA0B;CACzD,OAAO,MAAM,UAAU,GAAG,CAAC;AAC5B;AAEA,SAAgB,eAAe,UAA0B;CACxD,OAAO,WAAW,KAAK,MAAM,QAAQ;AACtC;AAEA,SAAgB,UAAU,OAAuB;CAChD,OAAO,OAAO,MAAM,YAAY,EAAE,CAAC;AACpC;AAGA,SAAgB,cAAc,OAAuB;CACpD,MAAM,CAAC,SAAS,IAAI,WAAW,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC;CAChE,MAAM,WAAW,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM;CAE5C,OAAO,KAAK,IAAI,GAAG,SAAS,SAAS,OAAO,QAAQ,CAAC;AACtD;AAGA,SAAgB,QAAQ,OAAe,QAAwB;CAC9D,MAAM,UAAU,OAAO,MAAM,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC;CAE3D,OAAO,YAAY,IAAI,IAAI;AAC5B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/lib/modifier-press.ts
|
|
2
|
+
const isApple = /^(Mac|iP)/.test(navigator.platform);
|
|
3
|
+
function isMenuPress(event) {
|
|
4
|
+
return isApple && event.ctrlKey;
|
|
5
|
+
}
|
|
6
|
+
function isResetPress(event) {
|
|
7
|
+
return isApple ? event.metaKey : event.ctrlKey;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { isMenuPress, isResetPress };
|
|
12
|
+
//# sourceMappingURL=modifier-press.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modifier-press.js","names":[],"sources":["../../../src/lib/modifier-press.ts"],"sourcesContent":["const isApple = /^(Mac|iP)/.test(navigator.platform);\n\n// macOS sends Ctrl-click as a primary button press and opens the context menu\nexport function isMenuPress(event: MouseEvent): boolean {\n\treturn isApple && event.ctrlKey;\n}\n\nexport function isResetPress(event: MouseEvent): boolean {\n\treturn isApple ? event.metaKey : event.ctrlKey;\n}\n"],"mappings":";AAAA,MAAM,UAAU,YAAY,KAAK,UAAU,QAAQ;AAGnD,SAAgB,YAAY,OAA4B;CACvD,OAAO,WAAW,MAAM;AACzB;AAEA,SAAgB,aAAa,OAA4B;CACxD,OAAO,UAAU,MAAM,UAAU,MAAM;AACxC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib/number-list.ts
|
|
2
|
+
function parseNumberList(text) {
|
|
3
|
+
if (text === null) return void 0;
|
|
4
|
+
const numbers = [...new Set(text.split(/[\s,]+/).filter(Boolean).map(Number).filter((entry) => Number.isFinite(entry)))].toSorted((first, second) => first - second);
|
|
5
|
+
return numbers.length >= 2 ? numbers : void 0;
|
|
6
|
+
}
|
|
7
|
+
function nearestPosition(positions, value) {
|
|
8
|
+
let nearest = positions[0] ?? value;
|
|
9
|
+
for (const position of positions) if (Math.abs(position - value) < Math.abs(nearest - value)) nearest = position;
|
|
10
|
+
return nearest;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { nearestPosition, parseNumberList };
|
|
15
|
+
//# sourceMappingURL=number-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-list.js","names":[],"sources":["../../../src/lib/number-list.ts"],"sourcesContent":["export function parseNumberList(text: null | string): Array<number> | undefined {\n\tif (text === null) return undefined;\n\n\tconst numbers = [\n\t\t...new Set(\n\t\t\ttext\n\t\t\t\t.split(/[\\s,]+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.map(Number)\n\t\t\t\t.filter((entry) => Number.isFinite(entry)),\n\t\t),\n\t].toSorted((first, second) => first - second);\n\n\treturn numbers.length >= 2 ? numbers : undefined;\n}\n\nexport function nearestPosition(positions: ReadonlyArray<number>, value: number): number {\n\tlet nearest = positions[0] ?? value;\n\n\tfor (const position of positions) {\n\t\tif (Math.abs(position - value) < Math.abs(nearest - value)) nearest = position;\n\t}\n\n\treturn nearest;\n}\n"],"mappings":";AAAA,SAAgB,gBAAgB,MAAgD;CAC/E,IAAI,SAAS,MAAM,OAAO;CAE1B,MAAM,UAAU,CACf,GAAG,IAAI,IACN,KACE,MAAM,QAAQ,CAAC,CACf,OAAO,OAAO,CAAC,CACf,IAAI,MAAM,CAAC,CACX,QAAQ,UAAU,OAAO,SAAS,KAAK,CAAC,CAC3C,CACD,CAAC,CAAC,UAAU,OAAO,WAAW,QAAQ,MAAM;CAE5C,OAAO,QAAQ,UAAU,IAAI,UAAU;AACxC;AAEA,SAAgB,gBAAgB,WAAkC,OAAuB;CACxF,IAAI,UAAU,UAAU,MAAM;CAE9B,KAAK,MAAM,YAAY,WACtB,IAAI,KAAK,IAAI,WAAW,KAAK,IAAI,KAAK,IAAI,UAAU,KAAK,GAAG,UAAU;CAGvE,OAAO;AACR"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/lib/pointer-drag.ts
|
|
2
|
+
function bindDrag(target, handlers, signal) {
|
|
3
|
+
let held;
|
|
4
|
+
const end = () => {
|
|
5
|
+
if (!held) return;
|
|
6
|
+
const { drag } = held;
|
|
7
|
+
held = void 0;
|
|
8
|
+
handlers.toggle(false);
|
|
9
|
+
handlers.release(drag);
|
|
10
|
+
};
|
|
11
|
+
const whileHeld = (type, handle) => {
|
|
12
|
+
target.addEventListener(type, (event) => {
|
|
13
|
+
if (held?.pointerId === event.pointerId) handle(held.drag, event);
|
|
14
|
+
}, { signal });
|
|
15
|
+
};
|
|
16
|
+
target.addEventListener("pointerdown", (event) => {
|
|
17
|
+
if (event.button !== 0 || held && held.pointerId !== event.pointerId) return;
|
|
18
|
+
end();
|
|
19
|
+
const drag = handlers.grab(event);
|
|
20
|
+
if (drag === void 0) return;
|
|
21
|
+
held = {
|
|
22
|
+
drag,
|
|
23
|
+
pointerId: event.pointerId
|
|
24
|
+
};
|
|
25
|
+
target.setPointerCapture(event.pointerId);
|
|
26
|
+
handlers.toggle(true);
|
|
27
|
+
}, { signal });
|
|
28
|
+
whileHeld("pointermove", handlers.move);
|
|
29
|
+
whileHeld("pointercancel", (drag) => {
|
|
30
|
+
handlers.cancel(drag);
|
|
31
|
+
end();
|
|
32
|
+
});
|
|
33
|
+
whileHeld("lostpointercapture", end);
|
|
34
|
+
whileHeld("pointerup", end);
|
|
35
|
+
signal.addEventListener("abort", end, { once: true });
|
|
36
|
+
return {
|
|
37
|
+
current: () => held?.drag,
|
|
38
|
+
end
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { bindDrag };
|
|
44
|
+
//# sourceMappingURL=pointer-drag.js.map
|
|
@@ -0,0 +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\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\ttarget.setPointerCapture(event.pointerId);\n\t\t\thandlers.toggle(true);\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', end);\n\tsignal.addEventListener('abort', end, { once: true });\n\n\treturn { current: () => held?.drag, end };\n}\n"],"mappings":";AAaA,SAAgB,SACf,QACA,UACA,QACoB;CACpB,IAAI;CAEJ,MAAM,YAAkB;EACvB,IAAI,CAAC,MAAM;EAEX,MAAM,EAAE,SAAS;EAEjB,OAAO;EACP,SAAS,OAAO,KAAK;EACrB,SAAS,QAAQ,IAAI;CACtB;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,OAAO,kBAAkB,MAAM,SAAS;EACxC,SAAS,OAAO,IAAI;CACrB,GACA,EAAE,OAAO,CACV;CACA,UAAU,eAAe,SAAS,IAAI;CACtC,UAAU,kBAAkB,SAAS;EACpC,SAAS,OAAO,IAAI;EACpB,IAAI;CACL,CAAC;CACD,UAAU,sBAAsB,GAAG;CACnC,UAAU,aAAa,GAAG;CAC1B,OAAO,iBAAiB,SAAS,KAAK,EAAE,MAAM,KAAK,CAAC;CAEpD,OAAO;EAAE,eAAe,MAAM;EAAM;CAAI;AACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-px-property.js","names":[],"sources":["../../../src/lib/read-px-property.ts"],"sourcesContent":["// Registered
|
|
1
|
+
{"version":3,"file":"read-px-property.js","names":[],"sources":["../../../src/lib/read-px-property.ts"],"sourcesContent":["// Registered properties only; a registered `<length>` always computes to px\nexport function readPxProperty(\n\tstyles: CSSStyleDeclaration,\n\tproperty: string,\n\tfallback: number,\n): number {\n\t// eslint-disable-next-line unicorn/prefer-number-coercion -- parsing has to stop at the unit\n\tconst parsed = Number.parseFloat(styles.getPropertyValue(property));\n\n\treturn Number.isFinite(parsed) ? parsed : fallback;\n}\n"],"mappings":";AACA,SAAgB,eACf,QACA,UACA,UACS;CAET,MAAM,SAAS,OAAO,WAAW,OAAO,iBAAiB,QAAQ,CAAC;CAElE,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC3C"}
|
package/dist/dev/lib/render.js
CHANGED
|
@@ -13,15 +13,30 @@ function template(html, root) {
|
|
|
13
13
|
return clone;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const
|
|
16
|
+
function placeChildren(parent, children) {
|
|
17
|
+
const dropped = [...parent.childNodes].filter((child) => !children.includes(child));
|
|
18
|
+
for (const child of dropped) child.remove();
|
|
19
|
+
for (const [index, child] of children.entries()) {
|
|
20
|
+
const current = parent.childNodes[index];
|
|
21
|
+
if (current === child) continue;
|
|
22
|
+
if (current) current.before(child);
|
|
23
|
+
else parent.append(child);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function attachSlots(host) {
|
|
27
|
+
const shown = document.createElement("slot");
|
|
28
|
+
const kept = document.createElement("slot");
|
|
29
|
+
kept.style.cssText = "position: absolute; display: block; inline-size: 0; block-size: 0; overflow: hidden; visibility: hidden";
|
|
18
30
|
host.attachShadow({
|
|
19
31
|
mode: "open",
|
|
20
32
|
slotAssignment: "manual"
|
|
21
|
-
}).append(
|
|
22
|
-
return
|
|
33
|
+
}).append(shown, kept);
|
|
34
|
+
return {
|
|
35
|
+
kept,
|
|
36
|
+
shown
|
|
37
|
+
};
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
//#endregion
|
|
26
|
-
export {
|
|
41
|
+
export { attachSlots, placeChildren, requireChild, template };
|
|
27
42
|
//# sourceMappingURL=render.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","names":[],"sources":["../../../src/lib/render.ts"],"sourcesContent":["export function requireChild<Child extends Element>(\n\troot: Element,\n\tselector: string,\n\tchild: new () => Child,\n): Child {\n\tconst found = root.querySelector(selector);\n\tif (!(found instanceof child)) throw new Error(`A template is missing its ${selector}`);\n\n\treturn found;\n}\n\nexport function template<Root extends Element>(html: string, root: new () => Root): () => Root {\n\tconst parsed = document.createElement('template');\n\n\tparsed.innerHTML = html;\n\n\treturn () => {\n\t\tconst clone = document.importNode(parsed.content, true).firstElementChild;\n\t\tif (!(clone instanceof root)) throw new Error(`A template's root is not a ${root.name}`);\n\n\t\treturn clone;\n\t};\n}\n\n//
|
|
1
|
+
{"version":3,"file":"render.js","names":[],"sources":["../../../src/lib/render.ts"],"sourcesContent":["export function requireChild<Child extends Element>(\n\troot: Element,\n\tselector: string,\n\tchild: new () => Child,\n): Child {\n\tconst found = root.querySelector(selector);\n\tif (!(found instanceof child)) throw new Error(`A template is missing its ${selector}`);\n\n\treturn found;\n}\n\nexport function template<Root extends Element>(html: string, root: new () => Root): () => Root {\n\tconst parsed = document.createElement('template');\n\n\tparsed.innerHTML = html;\n\n\treturn () => {\n\t\tconst clone = document.importNode(parsed.content, true).firstElementChild;\n\t\tif (!(clone instanceof root)) throw new Error(`A template's root is not a ${root.name}`);\n\n\t\treturn clone;\n\t};\n}\n\nexport function placeChildren(parent: Element, children: Array<Node>): void {\n\tconst dropped = [...parent.childNodes].filter((child) => !children.includes(child));\n\n\tfor (const child of dropped) child.remove();\n\tfor (const [index, child] of children.entries()) {\n\t\tconst current = parent.childNodes[index];\n\n\t\t// A node taken out and put back restarts its animations\n\t\tif (current === child) continue;\n\n\t\tif (current) current.before(child);\n\t\telse parent.append(child);\n\t}\n}\n\nexport interface MirrorSlots {\n\tkept: HTMLSlotElement;\n\tshown: HTMLSlotElement;\n}\n\n// An id resolves to the first element in the tree that carries it, and paints nothing when that one has no box\nexport function attachSlots(host: HTMLElement): MirrorSlots {\n\tconst shown = document.createElement('slot');\n\tconst kept = document.createElement('slot');\n\n\tkept.style.cssText =\n\t\t'position: absolute; display: block; inline-size: 0; block-size: 0; overflow: hidden; visibility: hidden';\n\thost.attachShadow({ mode: 'open', slotAssignment: 'manual' }).append(shown, kept);\n\n\treturn { kept, shown };\n}\n"],"mappings":";AAAA,SAAgB,aACf,MACA,UACA,OACQ;CACR,MAAM,QAAQ,KAAK,cAAc,QAAQ;CACzC,IAAI,EAAE,iBAAiB,QAAQ,MAAM,IAAI,MAAM,6BAA6B,UAAU;CAEtF,OAAO;AACR;AAEA,SAAgB,SAA+B,MAAc,MAAkC;CAC9F,MAAM,SAAS,SAAS,cAAc,UAAU;CAEhD,OAAO,YAAY;CAEnB,aAAa;EACZ,MAAM,QAAQ,SAAS,WAAW,OAAO,SAAS,IAAI,CAAC,CAAC;EACxD,IAAI,EAAE,iBAAiB,OAAO,MAAM,IAAI,MAAM,8BAA8B,KAAK,MAAM;EAEvF,OAAO;CACR;AACD;AAEA,SAAgB,cAAc,QAAiB,UAA6B;CAC3E,MAAM,UAAU,CAAC,GAAG,OAAO,UAAU,CAAC,CAAC,QAAQ,UAAU,CAAC,SAAS,SAAS,KAAK,CAAC;CAElF,KAAK,MAAM,SAAS,SAAS,MAAM,OAAO;CAC1C,KAAK,MAAM,CAAC,OAAO,UAAU,SAAS,QAAQ,GAAG;EAChD,MAAM,UAAU,OAAO,WAAW;EAGlC,IAAI,YAAY,OAAO;EAEvB,IAAI,SAAS,QAAQ,OAAO,KAAK;OAC5B,OAAO,OAAO,KAAK;CACzB;AACD;AAQA,SAAgB,YAAY,MAAgC;CAC3D,MAAM,QAAQ,SAAS,cAAc,MAAM;CAC3C,MAAM,OAAO,SAAS,cAAc,MAAM;CAE1C,KAAK,MAAM,UACV;CACD,KAAK,aAAa;EAAE,MAAM;EAAQ,gBAAgB;CAAS,CAAC,CAAC,CAAC,OAAO,OAAO,IAAI;CAEhF,OAAO;EAAE;EAAM;CAAM;AACtB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/lib/resample-peaks.ts
|
|
2
|
+
function bucketBounds(sourceLength, index, count) {
|
|
3
|
+
const start = Math.floor(index * sourceLength / count);
|
|
4
|
+
return [start, Math.max(start + 1, Math.floor((index + 1) * sourceLength / count))];
|
|
5
|
+
}
|
|
6
|
+
function downsample(peaks, count) {
|
|
7
|
+
const resampled = [];
|
|
8
|
+
for (let index = 0; index < count; index += 1) {
|
|
9
|
+
const [start, end] = bucketBounds(peaks.length, index, count);
|
|
10
|
+
let total = 0;
|
|
11
|
+
for (let source = start; source < end; source += 1) total += peaks[source] ?? 0;
|
|
12
|
+
resampled.push(total / (end - start));
|
|
13
|
+
}
|
|
14
|
+
return resampled;
|
|
15
|
+
}
|
|
16
|
+
function upsample(peaks, count) {
|
|
17
|
+
const resampled = [];
|
|
18
|
+
const last = peaks.length - 1;
|
|
19
|
+
for (let index = 0; index < count; index += 1) {
|
|
20
|
+
const sourceIndex = count === 1 ? 0 : index * last / (count - 1);
|
|
21
|
+
const before = Math.floor(sourceIndex);
|
|
22
|
+
const weight = sourceIndex - before;
|
|
23
|
+
resampled.push((peaks[before] ?? 0) * (1 - weight) + (peaks[Math.min(last, before + 1)] ?? 0) * weight);
|
|
24
|
+
}
|
|
25
|
+
return resampled;
|
|
26
|
+
}
|
|
27
|
+
function resamplePeaks(peaks, count) {
|
|
28
|
+
if (count <= 0 || peaks.length === 0) return [];
|
|
29
|
+
const finite = Array.from(peaks, (peak) => Number.isFinite(peak) ? peak : 0);
|
|
30
|
+
if (count === finite.length) return finite;
|
|
31
|
+
if (count > finite.length) return upsample(finite, count);
|
|
32
|
+
return downsample(finite, count);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { resamplePeaks };
|
|
37
|
+
//# sourceMappingURL=resample-peaks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample-peaks.js","names":[],"sources":["../../../src/lib/resample-peaks.ts"],"sourcesContent":["function bucketBounds(sourceLength: number, index: number, count: number): [number, number] {\n\tconst start = Math.floor((index * sourceLength) / count);\n\n\treturn [start, Math.max(start + 1, Math.floor(((index + 1) * sourceLength) / count))];\n}\n\nfunction downsample(peaks: ArrayLike<number>, count: number): Array<number> {\n\tconst resampled: Array<number> = [];\n\n\tfor (let index = 0; index < count; index += 1) {\n\t\tconst [start, end] = bucketBounds(peaks.length, index, count);\n\t\tlet total = 0;\n\n\t\tfor (let source = start; source < end; source += 1) total += peaks[source] ?? 0;\n\t\tresampled.push(total / (end - start));\n\t}\n\n\treturn resampled;\n}\n\nfunction upsample(peaks: ArrayLike<number>, count: number): Array<number> {\n\tconst resampled: Array<number> = [];\n\tconst last = peaks.length - 1;\n\n\tfor (let index = 0; index < count; index += 1) {\n\t\tconst sourceIndex = count === 1 ? 0 : (index * last) / (count - 1);\n\t\tconst before = Math.floor(sourceIndex);\n\t\tconst weight = sourceIndex - before;\n\n\t\tresampled.push(\n\t\t\t(peaks[before] ?? 0) * (1 - weight) + (peaks[Math.min(last, before + 1)] ?? 0) * weight,\n\t\t);\n\t}\n\n\treturn resampled;\n}\n\nexport function resamplePeaks(peaks: ArrayLike<number>, count: number): Array<number> {\n\tif (count <= 0 || peaks.length === 0) return [];\n\n\tconst finite = Array.from(peaks, (peak) => (Number.isFinite(peak) ? peak : 0));\n\n\tif (count === finite.length) return finite;\n\tif (count > finite.length) return upsample(finite, count);\n\n\treturn downsample(finite, count);\n}\n"],"mappings":";AAAA,SAAS,aAAa,cAAsB,OAAe,OAAiC;CAC3F,MAAM,QAAQ,KAAK,MAAO,QAAQ,eAAgB,KAAK;CAEvD,OAAO,CAAC,OAAO,KAAK,IAAI,QAAQ,GAAG,KAAK,OAAQ,QAAQ,KAAK,eAAgB,KAAK,CAAC,CAAC;AACrF;AAEA,SAAS,WAAW,OAA0B,OAA8B;CAC3E,MAAM,YAA2B,CAAC;CAElC,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;EAC9C,MAAM,CAAC,OAAO,OAAO,aAAa,MAAM,QAAQ,OAAO,KAAK;EAC5D,IAAI,QAAQ;EAEZ,KAAK,IAAI,SAAS,OAAO,SAAS,KAAK,UAAU,GAAG,SAAS,MAAM,WAAW;EAC9E,UAAU,KAAK,SAAS,MAAM,MAAM;CACrC;CAEA,OAAO;AACR;AAEA,SAAS,SAAS,OAA0B,OAA8B;CACzE,MAAM,YAA2B,CAAC;CAClC,MAAM,OAAO,MAAM,SAAS;CAE5B,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;EAC9C,MAAM,cAAc,UAAU,IAAI,IAAK,QAAQ,QAAS,QAAQ;EAChE,MAAM,SAAS,KAAK,MAAM,WAAW;EACrC,MAAM,SAAS,cAAc;EAE7B,UAAU,MACR,MAAM,WAAW,MAAM,IAAI,WAAW,MAAM,KAAK,IAAI,MAAM,SAAS,CAAC,MAAM,KAAK,MAClF;CACD;CAEA,OAAO;AACR;AAEA,SAAgB,cAAc,OAA0B,OAA8B;CACrF,IAAI,SAAS,KAAK,MAAM,WAAW,GAAG,OAAO,CAAC;CAE9C,MAAM,SAAS,MAAM,KAAK,QAAQ,SAAU,OAAO,SAAS,IAAI,IAAI,OAAO,CAAE;CAE7E,IAAI,UAAU,OAAO,QAAQ,OAAO;CACpC,IAAI,QAAQ,OAAO,QAAQ,OAAO,SAAS,QAAQ,KAAK;CAExD,OAAO,WAAW,QAAQ,KAAK;AAChC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { clampProportion } from "./math.js";
|
|
2
|
+
import { resamplePeaks } from "./resample-peaks.js";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/strip-scene.ts
|
|
5
|
+
function stripBars(peaks, size, grid) {
|
|
6
|
+
const pitch = Math.max(1, Math.round(grid.pitch * size.dpr));
|
|
7
|
+
const gap = Math.min(pitch - 1, Math.max(0, Math.round(pitch * grid.gapRatio)));
|
|
8
|
+
const width = pitch - gap;
|
|
9
|
+
const count = Math.max(0, Math.floor((size.width + gap) / pitch));
|
|
10
|
+
return resamplePeaks(peaks, count).map((peak, index) => {
|
|
11
|
+
const height = Math.max(1, Math.round(clampProportion(peak) * size.height));
|
|
12
|
+
return {
|
|
13
|
+
height,
|
|
14
|
+
radius: width * grid.radiusRatio,
|
|
15
|
+
width,
|
|
16
|
+
x: index * pitch,
|
|
17
|
+
y: Math.round((size.height - height) / 2)
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function stripRegions(state, widthPx) {
|
|
22
|
+
const px = (proportion) => Math.round((Number.isFinite(proportion) ? proportion : 0) * widthPx);
|
|
23
|
+
const buffered = state.buffered.map(([start, end]) => [px(start), px(end)]).filter(([start, end]) => end > start);
|
|
24
|
+
const played = px(state.played);
|
|
25
|
+
const scrub = state.scrub === void 0 ? played : px(state.scrub);
|
|
26
|
+
const regions = buffered.map(([from, to]) => ({
|
|
27
|
+
from,
|
|
28
|
+
kind: "buffered",
|
|
29
|
+
to
|
|
30
|
+
}));
|
|
31
|
+
if (played > 0) regions.push({
|
|
32
|
+
from: 0,
|
|
33
|
+
kind: "played",
|
|
34
|
+
to: played
|
|
35
|
+
});
|
|
36
|
+
if (scrub !== played) regions.push({
|
|
37
|
+
from: Math.min(played, scrub),
|
|
38
|
+
kind: "scrub",
|
|
39
|
+
to: Math.max(played, scrub)
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
key: `${String(played)}:${String(scrub)}:${buffered.join(",")}`,
|
|
43
|
+
regions
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { stripBars, stripRegions };
|
|
49
|
+
//# sourceMappingURL=strip-scene.js.map
|
|
@@ -0,0 +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"}
|
package/dist/dev/lib/taper.js
CHANGED
|
@@ -1,36 +1,52 @@
|
|
|
1
|
+
import { clampProportion } from "./math.js";
|
|
2
|
+
|
|
1
3
|
//#region src/lib/taper.ts
|
|
2
|
-
function clampUnit(fraction) {
|
|
3
|
-
return Math.min(1, Math.max(0, fraction));
|
|
4
|
-
}
|
|
5
|
-
function wrapUnit(fraction) {
|
|
6
|
-
return fraction - Math.floor(fraction);
|
|
7
|
-
}
|
|
8
4
|
function linearTaper(min, max) {
|
|
9
5
|
const range = max - min;
|
|
10
6
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
proportionOf: (value) => range > 0 ? clampProportion((value - min) / range) : 0,
|
|
8
|
+
valueAt: (proportion) => min + clampProportion(proportion) * range
|
|
13
9
|
};
|
|
14
10
|
}
|
|
15
11
|
function logTaper(min, max) {
|
|
16
12
|
if (min <= 0 || max <= min) return void 0;
|
|
17
13
|
const span = Math.log(max / min);
|
|
18
14
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
proportionOf: (value) => clampProportion(Math.log(Math.max(min, value) / min) / span),
|
|
16
|
+
valueAt: (proportion) => {
|
|
17
|
+
const clamped = clampProportion(proportion);
|
|
18
|
+
if (clamped === 0) return min;
|
|
19
|
+
if (clamped === 1) return max;
|
|
20
|
+
return min * Math.exp(clamped * span);
|
|
21
|
+
}
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
function skewTaper(min, max, midpoint) {
|
|
24
25
|
const range = max - min;
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
const skew = Math.log(.5) / Math.log(
|
|
26
|
+
const midpointProportion = (midpoint - min) / range;
|
|
27
|
+
if (midpointProportion === .5 || !(midpointProportion > 0 && midpointProportion < 1)) return void 0;
|
|
28
|
+
const skew = Math.log(.5) / Math.log(midpointProportion);
|
|
29
|
+
return {
|
|
30
|
+
proportionOf: (value) => clampProportion((value - min) / range) ** skew,
|
|
31
|
+
valueAt: (proportion) => min + range * clampProportion(proportion) ** (1 / skew)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function listTaper(positions) {
|
|
35
|
+
const last = positions.length - 1;
|
|
28
36
|
return {
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
proportionOf: (value) => {
|
|
38
|
+
if (Number.isNaN(value)) return NaN;
|
|
39
|
+
const above = positions.findIndex((position) => position >= value);
|
|
40
|
+
if (above === -1) return 1;
|
|
41
|
+
const high = positions[above];
|
|
42
|
+
const low = positions[above - 1];
|
|
43
|
+
if (high === void 0 || low === void 0) return 0;
|
|
44
|
+
return (above - 1 + (value - low) / (high - low)) / last;
|
|
45
|
+
},
|
|
46
|
+
valueAt: (proportion) => positions[Math.round(clampProportion(proportion) * last)] ?? NaN
|
|
31
47
|
};
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
//#endregion
|
|
35
|
-
export {
|
|
51
|
+
export { linearTaper, listTaper, logTaper, skewTaper };
|
|
36
52
|
//# sourceMappingURL=taper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taper.js","names":[],"sources":["../../../src/lib/taper.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"taper.js","names":[],"sources":["../../../src/lib/taper.ts"],"sourcesContent":["import { clampProportion } from '#lib/math.ts';\n\nexport interface Taper {\n\tproportionOf: (value: number) => number;\n\tvalueAt: (proportion: number) => number;\n}\n\nexport function linearTaper(min: number, max: number): Taper {\n\tconst range = max - min;\n\n\treturn {\n\t\tproportionOf: (value) => (range > 0 ? clampProportion((value - min) / range) : 0),\n\t\tvalueAt: (proportion) => min + clampProportion(proportion) * range,\n\t};\n}\n\nexport function logTaper(min: number, max: number): Taper | undefined {\n\tif (min <= 0 || max <= min) return undefined;\n\n\tconst span = Math.log(max / min);\n\n\treturn {\n\t\tproportionOf: (value) => clampProportion(Math.log(Math.max(min, value) / min) / span),\n\t\tvalueAt: (proportion) => {\n\t\t\tconst clamped = clampProportion(proportion);\n\t\t\tif (clamped === 0) return min;\n\t\t\tif (clamped === 1) return max;\n\n\t\t\treturn min * Math.exp(clamped * span);\n\t\t},\n\t};\n}\n\n// JUCE's skew: a power curve through `midpoint` at half the travel\nexport function skewTaper(min: number, max: number, midpoint: number): Taper | undefined {\n\tconst range = max - min;\n\tconst midpointProportion = (midpoint - min) / range;\n\n\tif (midpointProportion === 0.5 || !(midpointProportion > 0 && midpointProportion < 1))\n\t\treturn undefined;\n\n\tconst skew = Math.log(0.5) / Math.log(midpointProportion);\n\n\treturn {\n\t\tproportionOf: (value) => clampProportion((value - min) / range) ** skew,\n\t\tvalueAt: (proportion) => min + range * clampProportion(proportion) ** (1 / skew),\n\t};\n}\n\nexport function listTaper(positions: ReadonlyArray<number>): Taper {\n\tconst last = positions.length - 1;\n\n\treturn {\n\t\tproportionOf: (value) => {\n\t\t\tif (Number.isNaN(value)) return NaN;\n\n\t\t\tconst above = positions.findIndex((position) => position >= value);\n\t\t\tif (above === -1) return 1;\n\n\t\t\tconst high = positions[above];\n\t\t\tconst low = positions[above - 1];\n\t\t\tif (high === undefined || low === undefined) return 0;\n\n\t\t\treturn (above - 1 + (value - low) / (high - low)) / last;\n\t\t},\n\t\tvalueAt: (proportion) => positions[Math.round(clampProportion(proportion) * last)] ?? NaN,\n\t};\n}\n"],"mappings":";;;AAOA,SAAgB,YAAY,KAAa,KAAoB;CAC5D,MAAM,QAAQ,MAAM;CAEpB,OAAO;EACN,eAAe,UAAW,QAAQ,IAAI,iBAAiB,QAAQ,OAAO,KAAK,IAAI;EAC/E,UAAU,eAAe,MAAM,gBAAgB,UAAU,IAAI;CAC9D;AACD;AAEA,SAAgB,SAAS,KAAa,KAAgC;CACrE,IAAI,OAAO,KAAK,OAAO,KAAK,OAAO;CAEnC,MAAM,OAAO,KAAK,IAAI,MAAM,GAAG;CAE/B,OAAO;EACN,eAAe,UAAU,gBAAgB,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,IAAI;EACpF,UAAU,eAAe;GACxB,MAAM,UAAU,gBAAgB,UAAU;GAC1C,IAAI,YAAY,GAAG,OAAO;GAC1B,IAAI,YAAY,GAAG,OAAO;GAE1B,OAAO,MAAM,KAAK,IAAI,UAAU,IAAI;EACrC;CACD;AACD;AAGA,SAAgB,UAAU,KAAa,KAAa,UAAqC;CACxF,MAAM,QAAQ,MAAM;CACpB,MAAM,sBAAsB,WAAW,OAAO;CAE9C,IAAI,uBAAuB,MAAO,EAAE,qBAAqB,KAAK,qBAAqB,IAClF,OAAO;CAER,MAAM,OAAO,KAAK,IAAI,EAAG,IAAI,KAAK,IAAI,kBAAkB;CAExD,OAAO;EACN,eAAe,UAAU,iBAAiB,QAAQ,OAAO,KAAK,KAAK;EACnE,UAAU,eAAe,MAAM,QAAQ,gBAAgB,UAAU,MAAM,IAAI;CAC5E;AACD;AAEA,SAAgB,UAAU,WAAyC;CAClE,MAAM,OAAO,UAAU,SAAS;CAEhC,OAAO;EACN,eAAe,UAAU;GACxB,IAAI,OAAO,MAAM,KAAK,GAAG,OAAO;GAEhC,MAAM,QAAQ,UAAU,WAAW,aAAa,YAAY,KAAK;GACjE,IAAI,UAAU,IAAI,OAAO;GAEzB,MAAM,OAAO,UAAU;GACvB,MAAM,MAAM,UAAU,QAAQ;GAC9B,IAAI,SAAS,UAAa,QAAQ,QAAW,OAAO;GAEpD,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,OAAO,QAAQ;EACrD;EACA,UAAU,eAAe,UAAU,KAAK,MAAM,gBAAgB,UAAU,IAAI,IAAI,MAAM;CACvF;AACD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/lib/time-regions.ts
|
|
2
|
+
function readRegions(regions) {
|
|
3
|
+
if ("start" in regions) return Array.from({ length: regions.length }, (_entry, index) => [regions.start(index), regions.end(index)]);
|
|
4
|
+
return [...regions].map(([start, end]) => [start, end]);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
export { readRegions };
|
|
9
|
+
//# sourceMappingURL=time-regions.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/lib/tracking-clock.ts
|
|
2
|
+
const seekThresholdSeconds = .5;
|
|
3
|
+
const jitterSeconds = .05;
|
|
4
|
+
function isLandingOnSource({ isPlaying, seconds }, carried, lastSourceSeconds) {
|
|
5
|
+
const hasSteppedBack = lastSourceSeconds !== void 0 && lastSourceSeconds - seconds > jitterSeconds;
|
|
6
|
+
return !isPlaying || hasSteppedBack || Math.abs(seconds - carried) > seekThresholdSeconds;
|
|
7
|
+
}
|
|
8
|
+
function createTrackingClock() {
|
|
9
|
+
let positionSeconds;
|
|
10
|
+
let lastFrameMs;
|
|
11
|
+
let lastSourceSeconds;
|
|
12
|
+
let wasPlaying = false;
|
|
13
|
+
return { read: (frameMs, source) => {
|
|
14
|
+
const { isPlaying, playbackRate, seconds } = source;
|
|
15
|
+
const elapsedSeconds = wasPlaying ? Math.max(0, frameMs - (lastFrameMs ?? frameMs)) / 1e3 : 0;
|
|
16
|
+
const carried = (positionSeconds ?? seconds) + elapsedSeconds * playbackRate;
|
|
17
|
+
lastFrameMs = frameMs;
|
|
18
|
+
wasPlaying = isPlaying;
|
|
19
|
+
if (!Number.isFinite(seconds)) {
|
|
20
|
+
if (positionSeconds !== void 0) positionSeconds = carried;
|
|
21
|
+
return carried;
|
|
22
|
+
}
|
|
23
|
+
const isLanding = isLandingOnSource(source, carried, lastSourceSeconds);
|
|
24
|
+
lastSourceSeconds = seconds;
|
|
25
|
+
if (positionSeconds === void 0 || isLanding) {
|
|
26
|
+
positionSeconds = seconds;
|
|
27
|
+
return positionSeconds;
|
|
28
|
+
}
|
|
29
|
+
positionSeconds = carried + (seconds - carried) * (1 - .94 ** (elapsedSeconds * 60));
|
|
30
|
+
return positionSeconds;
|
|
31
|
+
} };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { createTrackingClock };
|
|
36
|
+
//# sourceMappingURL=tracking-clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracking-clock.js","names":[],"sources":["../../../src/lib/tracking-clock.ts"],"sourcesContent":["const seekThresholdSeconds = 0.5;\nconst jitterSeconds = 0.05;\n\n// The share of the error closed in a 60 Hz frame; scaled by elapsed time so every refresh rate converges alike\nconst catchUpPerFrame = 0.06;\n\ninterface ClockSource {\n\tisPlaying: boolean;\n\tplaybackRate: number;\n\tseconds: number;\n}\n\ninterface TrackingClock {\n\t// `frameMs` is the frame's timestamp, never `performance.now()` in the callback\n\tread: (frameMs: number, source: ClockSource) => number;\n}\n\nfunction isLandingOnSource(\n\t{ isPlaying, seconds }: ClockSource,\n\tcarried: number,\n\tlastSourceSeconds: number | undefined,\n): boolean {\n\tconst hasSteppedBack =\n\t\tlastSourceSeconds !== undefined && lastSourceSeconds - seconds > jitterSeconds;\n\n\treturn !isPlaying || hasSteppedBack || Math.abs(seconds - carried) > seekThresholdSeconds;\n}\n\nexport function createTrackingClock(): TrackingClock {\n\tlet positionSeconds: number | undefined;\n\tlet lastFrameMs: number | undefined;\n\tlet lastSourceSeconds: number | undefined;\n\tlet wasPlaying = false;\n\n\treturn {\n\t\tread: (frameMs, source) => {\n\t\t\tconst { isPlaying, playbackRate, seconds } = source;\n\t\t\tconst elapsedSeconds = wasPlaying\n\t\t\t\t? Math.max(0, frameMs - (lastFrameMs ?? frameMs)) / 1000\n\t\t\t\t: 0;\n\t\t\tconst carried = (positionSeconds ?? seconds) + elapsedSeconds * playbackRate;\n\n\t\t\tlastFrameMs = frameMs;\n\t\t\twasPlaying = isPlaying;\n\n\t\t\tif (!Number.isFinite(seconds)) {\n\t\t\t\tif (positionSeconds !== undefined) positionSeconds = carried;\n\t\t\t\treturn carried;\n\t\t\t}\n\n\t\t\tconst isLanding = isLandingOnSource(source, carried, lastSourceSeconds);\n\n\t\t\tlastSourceSeconds = seconds;\n\n\t\t\tif (positionSeconds === undefined || isLanding) {\n\t\t\t\tpositionSeconds = seconds;\n\t\t\t\treturn positionSeconds;\n\t\t\t}\n\n\t\t\tpositionSeconds =\n\t\t\t\tcarried + (seconds - carried) * (1 - (1 - catchUpPerFrame) ** (elapsedSeconds * 60));\n\n\t\t\treturn positionSeconds;\n\t\t},\n\t};\n}\n"],"mappings":";AAAA,MAAM,uBAAuB;AAC7B,MAAM,gBAAgB;AAgBtB,SAAS,kBACR,EAAE,WAAW,WACb,SACA,mBACU;CACV,MAAM,iBACL,sBAAsB,UAAa,oBAAoB,UAAU;CAElE,OAAO,CAAC,aAAa,kBAAkB,KAAK,IAAI,UAAU,OAAO,IAAI;AACtE;AAEA,SAAgB,sBAAqC;CACpD,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,aAAa;CAEjB,OAAO,EACN,OAAO,SAAS,WAAW;EAC1B,MAAM,EAAE,WAAW,cAAc,YAAY;EAC7C,MAAM,iBAAiB,aACpB,KAAK,IAAI,GAAG,WAAW,eAAe,QAAQ,IAAI,MAClD;EACH,MAAM,WAAW,mBAAmB,WAAW,iBAAiB;EAEhE,cAAc;EACd,aAAa;EAEb,IAAI,CAAC,OAAO,SAAS,OAAO,GAAG;GAC9B,IAAI,oBAAoB,QAAW,kBAAkB;GACrD,OAAO;EACR;EAEA,MAAM,YAAY,kBAAkB,QAAQ,SAAS,iBAAiB;EAEtE,oBAAoB;EAEpB,IAAI,oBAAoB,UAAa,WAAW;GAC/C,kBAAkB;GAClB,OAAO;EACR;EAEA,kBACC,WAAW,UAAU,YAAY,IAAK,QAAyB,iBAAiB;EAEjF,OAAO;CACR,EACD;AACD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/lib/value-mapping.d.ts
|
|
2
|
+
export interface ValueSpec {
|
|
3
|
+
detent?: number;
|
|
4
|
+
isNotched: boolean;
|
|
5
|
+
isWrapping: boolean;
|
|
6
|
+
max: number;
|
|
7
|
+
midpoint?: number;
|
|
8
|
+
min: number;
|
|
9
|
+
positions?: ReadonlyArray<number>;
|
|
10
|
+
step: number;
|
|
11
|
+
taper: 'linear' | 'log';
|
|
12
|
+
}
|
|
13
|
+
export interface ValueMapping {
|
|
14
|
+
readonly bounds: [number, number];
|
|
15
|
+
readonly isWrapping: boolean;
|
|
16
|
+
readonly keyTarget: (key: string, from: number, keyStep?: number) => number | undefined;
|
|
17
|
+
readonly positionCount: number | undefined;
|
|
18
|
+
readonly proportionOf: (value: number) => number;
|
|
19
|
+
readonly snap: (value: number) => number;
|
|
20
|
+
readonly valueAt: (proportion: number) => number;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//# sourceMappingURL=value-mapping.d.ts.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { clamp, decimalPlaces, roundTo, trimFloat } from "./math.js";
|
|
2
|
+
import { nearestPosition } from "./number-list.js";
|
|
3
|
+
import { linearTaper, listTaper, logTaper, skewTaper } from "./taper.js";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/value-mapping.ts
|
|
6
|
+
const resetKeys = /* @__PURE__ */ new Set(["Backspace", "Delete"]);
|
|
7
|
+
const keySteps = /* @__PURE__ */ new Map([
|
|
8
|
+
["ArrowDown", -1],
|
|
9
|
+
["ArrowLeft", -1],
|
|
10
|
+
["ArrowRight", 1],
|
|
11
|
+
["ArrowUp", 1],
|
|
12
|
+
["PageDown", -10],
|
|
13
|
+
["PageUp", 10]
|
|
14
|
+
]);
|
|
15
|
+
function curveOf(spec, max) {
|
|
16
|
+
const { min, positions } = spec;
|
|
17
|
+
if (positions) return listTaper(positions);
|
|
18
|
+
if (spec.isNotched || spec.isWrapping) return void 0;
|
|
19
|
+
return (spec.taper === "log" ? logTaper(min, max) : void 0) ?? skewTaper(min, max, spec.midpoint ?? NaN);
|
|
20
|
+
}
|
|
21
|
+
function countPositions(spec, count) {
|
|
22
|
+
if (!spec.isNotched || !Number.isFinite(count) || count < 2) return;
|
|
23
|
+
return count;
|
|
24
|
+
}
|
|
25
|
+
function countSteps(spec, range) {
|
|
26
|
+
if (spec.step <= 0) return NaN;
|
|
27
|
+
const fit = trimFloat(range / spec.step);
|
|
28
|
+
return spec.isWrapping ? Math.max(1, Math.ceil(fit)) : Math.floor(fit) + 1;
|
|
29
|
+
}
|
|
30
|
+
function valueMapping(spec) {
|
|
31
|
+
const { min, positions, step } = spec;
|
|
32
|
+
const max = Math.max(min, spec.max);
|
|
33
|
+
const range = max - min;
|
|
34
|
+
const count = countSteps(spec, range);
|
|
35
|
+
const isStepped = step > 0 && !positions;
|
|
36
|
+
const curve = curveOf(spec, max);
|
|
37
|
+
const taper = curve ?? linearTaper(min, max);
|
|
38
|
+
function round(value) {
|
|
39
|
+
if (step <= 0) return trimFloat(value);
|
|
40
|
+
return roundTo(value, Math.max(decimalPlaces(step), decimalPlaces(min)));
|
|
41
|
+
}
|
|
42
|
+
const top = round((count - 1) * step);
|
|
43
|
+
function limits() {
|
|
44
|
+
if (positions) return [positions[0] ?? min, positions.at(-1) ?? max];
|
|
45
|
+
return [min, isStepped && !spec.isWrapping ? round(min + top) : max];
|
|
46
|
+
}
|
|
47
|
+
const bounds = limits();
|
|
48
|
+
function wrap(value, span) {
|
|
49
|
+
const offset = round(((value - min) % span + span) % span);
|
|
50
|
+
return offset >= span ? 0 : offset;
|
|
51
|
+
}
|
|
52
|
+
function bound(value) {
|
|
53
|
+
if (!spec.isWrapping) return clamp(value, ...bounds);
|
|
54
|
+
if (range <= 0) return min;
|
|
55
|
+
return round(min + wrap(value, range));
|
|
56
|
+
}
|
|
57
|
+
function ring(value, span) {
|
|
58
|
+
if (range <= 0) return min;
|
|
59
|
+
const offset = wrap(value, span);
|
|
60
|
+
if (offset > top) return span - offset <= offset - top ? min : round(min + top);
|
|
61
|
+
return round(min + Math.round(offset / step) * step);
|
|
62
|
+
}
|
|
63
|
+
function snap(value) {
|
|
64
|
+
if (positions) return nearestPosition(positions, value);
|
|
65
|
+
if (step <= 0) return bound(round(value));
|
|
66
|
+
if (spec.isWrapping) return ring(value, range);
|
|
67
|
+
return bound(round(min + Math.round((value - min) / step) * step));
|
|
68
|
+
}
|
|
69
|
+
function stepBy(from, steps, size) {
|
|
70
|
+
if (positions) {
|
|
71
|
+
const index = clamp(positions.indexOf(from) + steps, 0, positions.length - 1);
|
|
72
|
+
return positions[index] ?? from;
|
|
73
|
+
}
|
|
74
|
+
if (!curve) return from + steps * size;
|
|
75
|
+
const next = snap(curve.valueAt(curve.proportionOf(from) + steps / 100));
|
|
76
|
+
return next === from ? from + Math.sign(steps) * size : next;
|
|
77
|
+
}
|
|
78
|
+
function stopAtDetent(from, next) {
|
|
79
|
+
if (spec.detent === void 0) return next;
|
|
80
|
+
const detent = snap(spec.detent);
|
|
81
|
+
const toward = Math.sign(next - from) * (detent - from);
|
|
82
|
+
const ahead = spec.isWrapping ? wrap(min + toward, isStepped ? count * step : range) : toward;
|
|
83
|
+
return ahead > 0 && ahead < Math.abs(next - from) ? detent : next;
|
|
84
|
+
}
|
|
85
|
+
function keyStride(keyStep) {
|
|
86
|
+
if (positions || !(keyStep > 0)) return void 0;
|
|
87
|
+
return step > 0 ? Math.max(1, Math.round(trimFloat(keyStep / step))) * step : keyStep;
|
|
88
|
+
}
|
|
89
|
+
function end(size) {
|
|
90
|
+
if (!spec.isWrapping) return bounds[1];
|
|
91
|
+
return min + (isStepped ? top : (Math.ceil(range / size) - 1) * size);
|
|
92
|
+
}
|
|
93
|
+
function target(key, from, keyStep) {
|
|
94
|
+
if (key === "Home") return bounds[0];
|
|
95
|
+
const size = step > 0 ? step : range / 100;
|
|
96
|
+
if (key === "End") return snap(end(size));
|
|
97
|
+
const steps = keySteps.get(key);
|
|
98
|
+
if (steps === void 0) return void 0;
|
|
99
|
+
const stride = keyStride(keyStep);
|
|
100
|
+
const next = stopAtDetent(from, stride === void 0 ? stepBy(from, steps, size) : from + steps * stride);
|
|
101
|
+
return isStepped && spec.isWrapping ? ring(next, count * step) : snap(next);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
bounds,
|
|
105
|
+
isWrapping: spec.isWrapping,
|
|
106
|
+
keyTarget: (key, from, keyStep = 0) => target(key, from, keyStep),
|
|
107
|
+
positionCount: countPositions(spec, positions?.length ?? count),
|
|
108
|
+
proportionOf: (value) => taper.proportionOf(bound(value)),
|
|
109
|
+
snap,
|
|
110
|
+
valueAt: (proportion) => taper.valueAt(proportion)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
export { resetKeys, valueMapping };
|
|
116
|
+
//# sourceMappingURL=value-mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-mapping.js","names":[],"sources":["../../../src/lib/value-mapping.ts"],"sourcesContent":["import type { Taper } from '#lib/taper.ts';\n\nimport { clamp, decimalPlaces, roundTo, trimFloat } from '#lib/math.ts';\nimport { nearestPosition } from '#lib/number-list.ts';\nimport { linearTaper, listTaper, logTaper, skewTaper } from '#lib/taper.ts';\n\nexport interface ValueSpec {\n\tdetent?: number;\n\tisNotched: boolean;\n\tisWrapping: boolean;\n\tmax: number;\n\tmidpoint?: number;\n\tmin: number;\n\tpositions?: ReadonlyArray<number>;\n\tstep: number;\n\ttaper: 'linear' | 'log';\n}\n\nexport interface ValueMapping {\n\treadonly bounds: [number, number];\n\treadonly isWrapping: boolean;\n\treadonly keyTarget: (key: string, from: number, keyStep?: number) => number | undefined;\n\treadonly positionCount: number | undefined;\n\treadonly proportionOf: (value: number) => number;\n\treadonly snap: (value: number) => number;\n\treadonly valueAt: (proportion: number) => number;\n}\n\n// A Mac keyboard's Delete sends Backspace\nexport const resetKeys = new Set(['Backspace', 'Delete']);\n\nconst pageSteps = 10;\n\nconst keySteps = new Map([\n\t['ArrowDown', -1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', 1],\n\t['PageDown', -pageSteps],\n\t['PageUp', pageSteps],\n]);\n\nfunction curveOf(spec: ValueSpec, max: number): Taper | undefined {\n\tconst { min, positions } = spec;\n\tif (positions) return listTaper(positions);\n\tif (spec.isNotched || spec.isWrapping) return undefined;\n\n\tconst log = spec.taper === 'log' ? logTaper(min, max) : undefined;\n\n\treturn log ?? skewTaper(min, max, spec.midpoint ?? NaN);\n}\n\nfunction countPositions(spec: ValueSpec, count: number): number | undefined {\n\tif (!spec.isNotched || !Number.isFinite(count) || count < 2) return;\n\n\treturn count;\n}\n\n// A wrapping mapping stops a step short of max, which is min again\nfunction countSteps(spec: ValueSpec, range: number): number {\n\tif (spec.step <= 0) return NaN;\n\n\tconst fit = trimFloat(range / spec.step);\n\n\treturn spec.isWrapping ? Math.max(1, Math.ceil(fit)) : Math.floor(fit) + 1;\n}\n\nexport function valueMapping(spec: ValueSpec): ValueMapping {\n\tconst { min, positions, step } = spec;\n\tconst max = Math.max(min, spec.max);\n\tconst range = max - min;\n\tconst count = countSteps(spec, range);\n\tconst isStepped = step > 0 && !positions;\n\tconst curve = curveOf(spec, max);\n\tconst taper = curve ?? linearTaper(min, max);\n\n\t// Decimal places, since significant digits keep the float residue beside 0\n\tfunction round(value: number): number {\n\t\tif (step <= 0) return trimFloat(value);\n\n\t\treturn roundTo(value, Math.max(decimalPlaces(step), decimalPlaces(min)));\n\t}\n\n\tconst top = round((count - 1) * step);\n\n\tfunction limits(): [number, number] {\n\t\tif (positions) return [positions[0] ?? min, positions.at(-1) ?? max];\n\n\t\treturn [min, isStepped && !spec.isWrapping ? round(min + top) : max];\n\t}\n\n\tconst bounds = limits();\n\n\tfunction wrap(value: number, span: number): number {\n\t\tconst offset = round((((value - min) % span) + span) % span);\n\n\t\treturn offset >= span ? 0 : offset;\n\t}\n\n\tfunction bound(value: number): number {\n\t\tif (!spec.isWrapping) return clamp(value, ...bounds);\n\t\tif (range <= 0) return min;\n\n\t\treturn round(min + wrap(value, range));\n\t}\n\n\t// A step that does not divide the range leaves a short seam, where a value goes to the nearer side\n\tfunction ring(value: number, span: number): number {\n\t\tif (range <= 0) return min;\n\n\t\tconst offset = wrap(value, span);\n\t\tif (offset > top) return span - offset <= offset - top ? min : round(min + top);\n\n\t\treturn round(min + Math.round(offset / step) * step);\n\t}\n\n\tfunction snap(value: number): number {\n\t\tif (positions) return nearestPosition(positions, value);\n\t\tif (step <= 0) return bound(round(value));\n\t\tif (spec.isWrapping) return ring(value, range);\n\n\t\treturn bound(round(min + Math.round((value - min) / step) * step));\n\t}\n\n\tfunction stepBy(from: number, steps: number, size: number): number {\n\t\tif (positions) {\n\t\t\tconst index = clamp(positions.indexOf(from) + steps, 0, positions.length - 1);\n\n\t\t\treturn positions[index] ?? from;\n\t\t}\n\t\tif (!curve) return from + steps * size;\n\n\t\tconst next = snap(curve.valueAt(curve.proportionOf(from) + steps / 100));\n\n\t\treturn next === from ? from + Math.sign(steps) * size : next;\n\t}\n\n\tfunction stopAtDetent(from: number, next: number): number {\n\t\tif (spec.detent === undefined) return next;\n\n\t\tconst detent = snap(spec.detent);\n\t\tconst toward = Math.sign(next - from) * (detent - from);\n\t\tconst ahead = spec.isWrapping ? wrap(min + toward, isStepped ? count * step : range) : toward;\n\n\t\treturn ahead > 0 && ahead < Math.abs(next - from) ? detent : next;\n\t}\n\n\tfunction keyStride(keyStep: number): number | undefined {\n\t\tif (positions || !(keyStep > 0)) return undefined;\n\n\t\treturn step > 0 ? Math.max(1, Math.round(trimFloat(keyStep / step))) * step : keyStep;\n\t}\n\n\tfunction end(size: number): number {\n\t\tif (!spec.isWrapping) return bounds[1];\n\n\t\treturn min + (isStepped ? top : (Math.ceil(range / size) - 1) * size);\n\t}\n\n\tfunction target(key: string, from: number, keyStep: number): number | undefined {\n\t\tif (key === 'Home') return bounds[0];\n\n\t\tconst size = step > 0 ? step : range / 100;\n\t\tif (key === 'End') return snap(end(size));\n\n\t\tconst steps = keySteps.get(key);\n\t\tif (steps === undefined) return undefined;\n\n\t\tconst stride = keyStride(keyStep);\n\t\tconst moved = stride === undefined ? stepBy(from, steps, size) : from + steps * stride;\n\t\tconst next = stopAtDetent(from, moved);\n\n\t\treturn isStepped && spec.isWrapping ? ring(next, count * step) : snap(next);\n\t}\n\n\treturn {\n\t\tbounds,\n\t\tisWrapping: spec.isWrapping,\n\t\tkeyTarget: (key, from, keyStep = 0) => target(key, from, keyStep),\n\t\tpositionCount: countPositions(spec, positions?.length ?? count),\n\t\tproportionOf: (value) => taper.proportionOf(bound(value)),\n\t\tsnap,\n\t\tvalueAt: (proportion) => taper.valueAt(proportion),\n\t};\n}\n"],"mappings":"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"}
|