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