@xsynaptic/sonic-ui 0.4.0 → 0.6.0

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