@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,367 @@
1
+ import { requireChild, template } from "../lib/render.js";
2
+ import { writeAttribute } from "../lib/write-attribute.js";
3
+ import { SonicElement } from "./sonic-element.js";
4
+ import { ReadoutClaim } from "./readout-claim.js";
5
+ import { Readout } from "./readout.js";
6
+ import { dragThresholdPx } from "../lib/drag-step.js";
7
+ import { SonicValueElement, linkValue } from "./value-element.js";
8
+ import { adsrPath, adsrShape, adsrStages, timeStages } from "../lib/adsr-shape.js";
9
+ import { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag } from "../lib/field.js";
10
+ import { bindDrag } from "../lib/pointer-drag.js";
11
+
12
+ //#region src/elements/envelope.ts
13
+ const curveStages = [
14
+ "attack",
15
+ "decay",
16
+ "release"
17
+ ];
18
+ const bindable = [
19
+ ...adsrStages,
20
+ "attack-curve",
21
+ "decay-curve",
22
+ "release-curve"
23
+ ];
24
+ const bindableNames = new Set(bindable);
25
+ function flip(point, isFlipped) {
26
+ return isFlipped ? {
27
+ ...point,
28
+ y: -point.y
29
+ } : point;
30
+ }
31
+ function isParent(node) {
32
+ return "querySelector" in node;
33
+ }
34
+ const renderEnvelope = template(`
35
+ <div class="sonic-envelope" aria-hidden="true">
36
+ <svg class="sonic-envelope-graph" preserveAspectRatio="none" viewBox="0 0 1 1">
37
+ <path class="sonic-envelope-fill" />
38
+ <path class="sonic-envelope-line" />
39
+ </svg>
40
+ <div class="sonic-envelope-handle" data-sonic-stage="delay" hidden></div>
41
+ <div class="sonic-envelope-handle" data-sonic-stage="attack" hidden></div>
42
+ <div class="sonic-envelope-handle" data-sonic-stage="hold" hidden></div>
43
+ <div class="sonic-envelope-handle" data-sonic-stage="decay" hidden></div>
44
+ <div class="sonic-envelope-handle" data-sonic-stage="release" hidden></div>
45
+ <div class="sonic-envelope-curve" data-sonic-stage="attack" hidden></div>
46
+ <div class="sonic-envelope-curve" data-sonic-stage="decay" hidden></div>
47
+ <div class="sonic-envelope-curve" data-sonic-stage="release" hidden></div>
48
+ <div class="sonic-envelope-readout" popover="manual"><span></span></div>
49
+ </div>
50
+ `, HTMLDivElement);
51
+ var SonicEnvelope = class extends SonicElement {
52
+ static observedAttributes = [
53
+ ...SonicElement.observedAttributes,
54
+ ...bindable,
55
+ "readout"
56
+ ];
57
+ get attack() {
58
+ return this.getAttribute("attack") ?? "";
59
+ }
60
+ set attack(id) {
61
+ this.reflect("attack", id);
62
+ }
63
+ get attackCurve() {
64
+ return this.getAttribute("attack-curve") ?? "";
65
+ }
66
+ set attackCurve(id) {
67
+ this.reflect("attack-curve", id);
68
+ }
69
+ get decay() {
70
+ return this.getAttribute("decay") ?? "";
71
+ }
72
+ set decay(id) {
73
+ this.reflect("decay", id);
74
+ }
75
+ get decayCurve() {
76
+ return this.getAttribute("decay-curve") ?? "";
77
+ }
78
+ set decayCurve(id) {
79
+ this.reflect("decay-curve", id);
80
+ }
81
+ get delay() {
82
+ return this.getAttribute("delay") ?? "";
83
+ }
84
+ set delay(id) {
85
+ this.reflect("delay", id);
86
+ }
87
+ get hold() {
88
+ return this.getAttribute("hold") ?? "";
89
+ }
90
+ set hold(id) {
91
+ this.reflect("hold", id);
92
+ }
93
+ get readout() {
94
+ return this.hasAttribute("readout");
95
+ }
96
+ set readout(isOn) {
97
+ this.reflect("readout", isOn);
98
+ }
99
+ get release() {
100
+ return this.getAttribute("release") ?? "";
101
+ }
102
+ set release(id) {
103
+ this.reflect("release", id);
104
+ }
105
+ get releaseCurve() {
106
+ return this.getAttribute("release-curve") ?? "";
107
+ }
108
+ set releaseCurve(id) {
109
+ this.reflect("release-curve", id);
110
+ }
111
+ get sustain() {
112
+ return this.getAttribute("sustain") ?? "";
113
+ }
114
+ set sustain(id) {
115
+ this.reflect("sustain", id);
116
+ }
117
+ #bindings = /* @__PURE__ */ new Map();
118
+ #claim = new ReadoutClaim(() => {
119
+ this.#renderReadout();
120
+ });
121
+ #envelope = renderEnvelope();
122
+ #fill = requireChild(this.#envelope, ".sonic-envelope-fill", SVGElement);
123
+ #graph = requireChild(this.#envelope, ".sonic-envelope-graph", SVGElement);
124
+ #line = requireChild(this.#envelope, ".sonic-envelope-line", SVGElement);
125
+ #pointerDrag;
126
+ #readout = new Readout(requireChild(this.#envelope, ".sonic-envelope-readout", HTMLDivElement));
127
+ #shape = adsrShape({});
128
+ #signal;
129
+ #waiting = /* @__PURE__ */ new Set();
130
+ #warned = /* @__PURE__ */ new Set();
131
+ attributeChangedCallback(name) {
132
+ if (!this.#signal) return;
133
+ if (name === "disabled") {
134
+ this.#pointerDrag?.end();
135
+ this.#draw();
136
+ return;
137
+ }
138
+ if (bindableNames.has(name)) this.#bind();
139
+ else this.#renderReadout();
140
+ }
141
+ connectedCallback() {
142
+ this.upgradeProperties(...adsrStages, "attackCurve", "decayCurve", "readout", "releaseCurve");
143
+ super.connectedCallback();
144
+ }
145
+ connect(signal) {
146
+ const envelope = this.#envelope;
147
+ this.#signal = signal;
148
+ this.appendOnce(envelope);
149
+ this.#bind();
150
+ this.checkStyles(envelope, "envelope.css");
151
+ this.#bindPointer(envelope, signal);
152
+ signal.addEventListener("abort", () => {
153
+ this.#signal = void 0;
154
+ this.#unbind();
155
+ }, { once: true });
156
+ }
157
+ #bind() {
158
+ this.#pointerDrag?.end();
159
+ this.#unbind();
160
+ for (const name of bindable) {
161
+ const element = this.#resolve(name);
162
+ if (!element) continue;
163
+ const link = linkValue(element);
164
+ const unwatch = link.watch(() => {
165
+ this.#draw();
166
+ });
167
+ this.#bindings.set(name, {
168
+ element,
169
+ link,
170
+ unwatch
171
+ });
172
+ }
173
+ this.#draw();
174
+ }
175
+ #bindPointer(envelope, signal) {
176
+ this.#pointerDrag = bindDrag(envelope, {
177
+ cancel: (drag) => {
178
+ for (const { binding, from } of drag.drives) binding.link.input(from);
179
+ },
180
+ grab: (event) => this.#grab(event),
181
+ move: (drag, event) => {
182
+ this.#moveDrag(drag, event);
183
+ },
184
+ release: (drag) => {
185
+ this.#claim.press(false);
186
+ for (const { binding, from } of drag.drives) {
187
+ if (binding.link.value() === from) continue;
188
+ binding.element.dispatchEvent(new Event("change", { bubbles: true }));
189
+ }
190
+ },
191
+ toggle: (isDragging) => {
192
+ this.toggleState("dragging", isDragging);
193
+ }
194
+ }, signal);
195
+ }
196
+ #curveDrives(stage) {
197
+ const binding = this.#bindings.get(`${stage}-curve`);
198
+ return binding ? [{
199
+ axis: "y",
200
+ binding
201
+ }] : [];
202
+ }
203
+ #draw() {
204
+ const shape = this.#readShape();
205
+ const path = this.#bindings.size === 0 ? {
206
+ fill: "",
207
+ stroke: ""
208
+ } : adsrPath(shape);
209
+ this.#shape = shape;
210
+ this.#line.setAttribute("d", path.stroke);
211
+ this.#fill.setAttribute("d", path.fill);
212
+ for (const stage of timeStages) this.#drawPoint(`.sonic-envelope-handle[data-sonic-stage="${stage}"]`, {
213
+ at: shape.handles.find((point) => point.stage === stage),
214
+ drives: this.#drives(stage)
215
+ });
216
+ for (const stage of curveStages) this.#drawPoint(`.sonic-envelope-curve[data-sonic-stage="${stage}"]`, {
217
+ at: shape.curveHandles.find((point) => point.stage === stage),
218
+ drives: this.#curveDrives(stage)
219
+ });
220
+ this.#renderReadout();
221
+ }
222
+ #drawPoint(selector, point) {
223
+ const part = requireChild(this.#envelope, selector, HTMLDivElement);
224
+ const { at, drives } = point;
225
+ part.hidden = !at;
226
+ if (!at) return;
227
+ const isStill = this.isDisabled() || drives.every(({ binding }) => binding.link.isDisabled());
228
+ part.style.setProperty("--_sonic-envelope-x", String(at.x));
229
+ part.style.setProperty("--_sonic-envelope-y", String(at.y));
230
+ part.dataset.sonicAxes = drives.map(({ axis }) => axis).join("");
231
+ writeAttribute(part, "data-sonic-disabled", isStill ? "" : void 0);
232
+ }
233
+ #drives(stage) {
234
+ const x = this.#bindings.get(stage);
235
+ const y = stage === "decay" ? this.#bindings.get("sustain") : void 0;
236
+ return [...x ? [{
237
+ axis: "x",
238
+ binding: x
239
+ }] : [], ...y ? [{
240
+ axis: "y",
241
+ binding: y
242
+ }] : []];
243
+ }
244
+ #grab(event) {
245
+ const pressed = this.#pressed(event);
246
+ if (!pressed || pressed.drives.length === 0) return void 0;
247
+ const { drives, isFlipped, part } = pressed;
248
+ this.#claim.press(true);
249
+ const box = this.#graph.getBoundingClientRect();
250
+ const travel = {
251
+ x: Math.max(1, box.width * this.#shape.share),
252
+ y: Math.max(1, box.height)
253
+ };
254
+ const start = (axis) => {
255
+ const link = drives.find((drive) => drive.axis === axis)?.binding.link;
256
+ if (!link) return;
257
+ const mapping = link.mapping();
258
+ const from = link.value();
259
+ return {
260
+ from,
261
+ mapping,
262
+ proportion: mapping.proportionOf(from),
263
+ travelPx: travel[axis]
264
+ };
265
+ };
266
+ return {
267
+ drives: drives.map((drive) => ({
268
+ ...drive,
269
+ from: drive.binding.link.value()
270
+ })),
271
+ isFlipped,
272
+ part,
273
+ state: startFieldDrag({
274
+ position: flip(pointerPosition(event), isFlipped),
275
+ thresholdPx: dragThresholdPx(event.pointerType),
276
+ x: start("x"),
277
+ y: start("y")
278
+ })
279
+ };
280
+ }
281
+ #moveDrag(drag, event) {
282
+ const mappings = {};
283
+ for (const { axis, binding } of drag.drives) mappings[axis] = binding.link.mapping();
284
+ const step = stepFieldDrag(mappings, drag.state, flip(pointerMove(event), drag.isFlipped));
285
+ drag.state = step.state;
286
+ if (step.state.isEngaged) this.#claim.reveal("drag");
287
+ for (const { axis, binding } of drag.drives) {
288
+ const next = step[axis];
289
+ if (next !== void 0) binding.link.input(next);
290
+ }
291
+ this.#renderReadout();
292
+ }
293
+ #pressed(event) {
294
+ const part = event.target instanceof Element ? event.target.closest(".sonic-envelope-handle, .sonic-envelope-curve") : void 0;
295
+ const stage = part?.dataset.sonicStage;
296
+ if (!part || stage === void 0 || this.isDisabled()) return void 0;
297
+ const isCurveHandle = part.classList.contains("sonic-envelope-curve");
298
+ return {
299
+ drives: (isCurveHandle ? this.#curveDrives(stage) : this.#drives(stage)).filter(({ binding }) => !binding.link.isDisabled()),
300
+ isFlipped: isCurveHandle && stage === "attack",
301
+ part
302
+ };
303
+ }
304
+ #readShape() {
305
+ const proportions = {};
306
+ const curves = {};
307
+ for (const stage of adsrStages) {
308
+ const link = this.#bindings.get(stage)?.link;
309
+ proportions[stage] = link?.mapping().proportionOf(link.value());
310
+ }
311
+ for (const stage of curveStages) curves[stage] = this.#bindings.get(`${stage}-curve`)?.link.value();
312
+ return adsrShape(proportions, curves);
313
+ }
314
+ #rebindOnce(key, arrives) {
315
+ if (this.#waiting.has(key)) return;
316
+ this.#waiting.add(key);
317
+ arrives(() => {
318
+ this.#waiting.delete(key);
319
+ if (this.#signal) this.#bind();
320
+ });
321
+ }
322
+ #renderReadout() {
323
+ const drag = this.#pointerDrag?.current();
324
+ if (!drag) {
325
+ this.#readout.close();
326
+ return;
327
+ }
328
+ this.#readout.show({
329
+ anchor: drag.part,
330
+ isOpen: this.#claim.isRevealed && this.readout,
331
+ text: drag.drives.map(({ binding }) => binding.element.valueText).join(", ")
332
+ });
333
+ }
334
+ #resolve(name) {
335
+ const id = this.getAttribute(name);
336
+ const root = this.getRootNode();
337
+ if (!id || !isParent(root)) return void 0;
338
+ const found = root.querySelector(`#${CSS.escape(id)}`);
339
+ if (found instanceof SonicValueElement) return found;
340
+ this.#wait(id, found);
341
+ }
342
+ #unbind() {
343
+ for (const { unwatch } of this.#bindings.values()) unwatch();
344
+ this.#bindings.clear();
345
+ }
346
+ #wait(id, found) {
347
+ const tag = found?.localName;
348
+ const { ownerDocument } = this;
349
+ if (tag?.includes("-") && !customElements.get(tag)) {
350
+ this.#rebindOnce(tag, (rebind) => {
351
+ customElements.whenDefined(tag).then(rebind);
352
+ });
353
+ return;
354
+ }
355
+ if (!found && ownerDocument.readyState === "loading") {
356
+ this.#rebindOnce(ownerDocument, (rebind) => {
357
+ ownerDocument.addEventListener("DOMContentLoaded", rebind, { once: true });
358
+ });
359
+ return;
360
+ }
361
+ this.#warn(id);
362
+ }
363
+ #warn(id) {}
364
+ };
365
+
366
+ //#endregion
367
+ export { SonicEnvelope };
@@ -1,40 +1,66 @@
1
1
  import { template } from "../lib/render.js";
2
2
  import { SonicElement } from "./sonic-element.js";
3
+ import { parseNumberList } from "../lib/number-list.js";
4
+ import { linearTaper } from "../lib/taper.js";
5
+ import { ballisticsRates, fall, rise, stepNeedle } from "../lib/ballistics.js";
3
6
 
4
7
  //#region src/elements/meter.ts
5
8
  const renderMeter = template(`
6
9
  <div aria-hidden="true" class="sonic-meter">
7
10
  <div class="sonic-meter-segments"></div>
8
- <div class="sonic-meter-level"></div>
11
+ <div class="sonic-meter-bar"></div>
9
12
  <div class="sonic-meter-clip"></div>
10
13
  </div>
11
14
  `, HTMLDivElement);
12
- const releaseDecibelsPerSecond = 20;
13
15
  const holdMs = 1500;
16
+ const ballisticsModes = [
17
+ "peak",
18
+ "ppm-1",
19
+ "ppm-2",
20
+ "vu"
21
+ ];
22
+ const settledDecibels = .01;
23
+ const settledAmplitude = 1e-4;
24
+ const settledPerSecond = .01;
14
25
  function toDecibels(amplitude) {
15
26
  return 20 * Math.log10(Math.abs(amplitude));
16
27
  }
28
+ function toAmplitude(decibels) {
29
+ return 10 ** (decibels / 20);
30
+ }
31
+ function segmentsBelow(segments, zone) {
32
+ return `calc(${segments.map((threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`).join(" + ")})`;
33
+ }
17
34
  var SonicMeter = class extends SonicElement {
18
35
  static observedAttributes = [
36
+ "ballistics",
19
37
  "disabled",
20
38
  "max",
21
- "min"
39
+ "min",
40
+ "origin",
41
+ "segments",
42
+ "value"
22
43
  ];
44
+ get ballistics() {
45
+ const name = this.getAttribute("ballistics");
46
+ return ballisticsModes.find((mode) => mode === name) ?? "peak";
47
+ }
48
+ set ballistics(mode) {
49
+ this.reflect("ballistics", mode);
50
+ }
23
51
  get level() {
24
52
  return this.#level;
25
53
  }
26
54
  set level(amplitude) {
27
55
  if (!Number.isFinite(amplitude)) return;
28
56
  const decibels = toDecibels(amplitude);
29
- const now = performance.now();
30
57
  this.#level = amplitude;
31
58
  this.#target = decibels;
32
- this.#bar = Math.max(this.#bar, decibels);
33
- if (decibels >= this.#peak) {
34
- this.#peak = decibels;
35
- this.#peakAt = now;
36
- }
59
+ if (this.#isValueShown) return;
60
+ const now = performance.now();
61
+ this.#peak = Math.max(this.#peak, decibels);
37
62
  if (decibels >= 0) this.#clipAt = now;
63
+ this.#step(now, 0);
38
64
  this.#render(now);
39
65
  this.#schedule(now);
40
66
  }
@@ -56,24 +82,57 @@ var SonicMeter = class extends SonicElement {
56
82
  set orientation(direction) {
57
83
  this.reflect("orientation", direction);
58
84
  }
85
+ get origin() {
86
+ return this.optionalNumberAttribute("origin");
87
+ }
88
+ set origin(value) {
89
+ this.reflect("origin", value);
90
+ }
91
+ get peak() {
92
+ return this.#peak;
93
+ }
94
+ get segments() {
95
+ return parseNumberList(this.getAttribute("segments"));
96
+ }
97
+ set segments(thresholds) {
98
+ this.reflect("segments", thresholds?.join(" "));
99
+ }
100
+ get value() {
101
+ return this.optionalNumberAttribute("value");
102
+ }
103
+ set value(value) {
104
+ this.reflect("value", value);
105
+ }
59
106
  #bar = -Infinity;
107
+ #carryMs = 0;
60
108
  #clipAt = -Infinity;
61
109
  #frame;
110
+ #isValueShown = false;
62
111
  #lastFrame;
63
112
  #level = 0;
64
113
  #meter = renderMeter();
65
114
  #peak = -Infinity;
66
- #peakAt = -Infinity;
115
+ #peakHold = -Infinity;
116
+ #peakHoldAt = -Infinity;
117
+ #segments;
67
118
  #target = -Infinity;
68
- attributeChangedCallback() {
119
+ #velocity = 0;
120
+ attributeChangedCallback(name) {
69
121
  const now = performance.now();
122
+ if (name === "ballistics") this.#velocity = 0;
123
+ else if (name === "value") this.#readValue(now);
124
+ this.#renderSegments();
70
125
  this.#render(now);
71
126
  this.#schedule(now);
72
127
  }
128
+ resetPeak() {
129
+ this.#peak = -Infinity;
130
+ }
73
131
  connect(signal) {
74
132
  const now = performance.now();
75
- this.upgradeProperties("max", "min", "orientation", "level");
133
+ this.upgradeProperties("ballistics", "max", "min", "orientation", "origin", "segments", "value", "level");
76
134
  this.appendOnce(this.#meter);
135
+ this.#renderSegments();
77
136
  this.#render(now);
78
137
  this.#schedule(now);
79
138
  this.checkStyles(this.#meter, "meter.css");
@@ -83,40 +142,123 @@ var SonicMeter = class extends SonicElement {
83
142
  this.#lastFrame = void 0;
84
143
  }, { once: true });
85
144
  }
86
- #fraction(decibels) {
87
- const min = this.min;
88
- const range = this.max - min;
89
- return range > 0 ? Math.min(1, Math.max(0, (decibels - min) / range)) : 0;
90
- }
91
145
  #isSettled(now) {
146
+ if (this.#isValueShown) return true;
147
+ if (now - this.#clipAt < holdMs) return false;
148
+ if (this.ballistics === "vu") return this.#bar === this.#target && this.#velocity === 0;
92
149
  const floor = this.min;
93
- return this.#bar <= Math.max(this.#target, floor) && this.#peak <= Math.max(this.#bar, floor) && now - this.#clipAt >= holdMs;
150
+ return this.#bar >= this.#target && this.#bar <= Math.max(this.#target, floor) && this.#peakHold <= Math.max(this.#bar, floor);
151
+ }
152
+ #proportionOf(level) {
153
+ const segments = this.#segments;
154
+ if (segments) return segments.filter((threshold) => threshold <= level).length / segments.length;
155
+ return linearTaper(this.min, this.max).proportionOf(level);
156
+ }
157
+ #readValue(now) {
158
+ const value = this.value;
159
+ const isValueShown = value !== void 0;
160
+ if (isValueShown !== this.#isValueShown) {
161
+ this.#isValueShown = isValueShown;
162
+ this.resetPeak();
163
+ if (!isValueShown) this.#step(now, 0);
164
+ }
165
+ if (value !== void 0) this.#peak = Math.max(this.#peak, value);
94
166
  }
95
167
  #render(now) {
96
168
  const meter = this.#meter;
169
+ const origin = this.origin === void 0 ? 0 : this.#proportionOf(this.origin);
97
170
  meter.style.setProperty("--_sonic-meter-min", String(this.min));
98
171
  meter.style.setProperty("--_sonic-meter-max", String(this.max));
172
+ meter.style.setProperty("--_sonic-meter-origin", String(origin));
99
173
  if (this.disabled) {
100
- meter.style.setProperty("--_sonic-meter-level", "0");
101
- meter.style.setProperty("--_sonic-meter-peak", "0");
102
- meter.style.setProperty("--_sonic-meter-clipped", "0");
174
+ this.#renderStill(origin);
175
+ return;
176
+ }
177
+ const value = this.value;
178
+ if (value !== void 0) {
179
+ this.#renderStill(this.#proportionOf(value));
103
180
  return;
104
181
  }
105
- meter.style.setProperty("--_sonic-meter-level", String(this.#fraction(this.#bar)));
106
- meter.style.setProperty("--_sonic-meter-peak", String(this.#fraction(this.#peak)));
182
+ const peakHold = this.ballistics === "vu" ? 0 : this.#proportionOf(this.#peakHold);
183
+ meter.style.setProperty("--_sonic-meter-bar", String(this.#proportionOf(this.#bar)));
184
+ meter.style.setProperty("--_sonic-meter-peak-hold", String(peakHold));
107
185
  meter.style.setProperty("--_sonic-meter-clipped", now - this.#clipAt < holdMs ? "1" : "0");
108
186
  }
187
+ #renderSegments() {
188
+ const meter = this.#meter;
189
+ const segments = this.segments;
190
+ this.#segments = segments;
191
+ this.toggleState("ladder", segments !== void 0);
192
+ if (!segments) {
193
+ for (const name of [
194
+ "count",
195
+ "hot-segments",
196
+ "clip-segments"
197
+ ]) meter.style.removeProperty(`--_sonic-meter-${name}`);
198
+ return;
199
+ }
200
+ meter.style.setProperty("--_sonic-meter-count", String(segments.length));
201
+ meter.style.setProperty("--_sonic-meter-hot-segments", segmentsBelow(segments, "hot"));
202
+ meter.style.setProperty("--_sonic-meter-clip-segments", segmentsBelow(segments, "clip"));
203
+ }
204
+ #renderStill(bar) {
205
+ const { style } = this.#meter;
206
+ style.setProperty("--_sonic-meter-bar", String(bar));
207
+ style.setProperty("--_sonic-meter-peak-hold", "0");
208
+ style.setProperty("--_sonic-meter-clipped", "0");
209
+ }
210
+ #risen(timeConstantMs, elapsedMs) {
211
+ const target = toAmplitude(this.#target);
212
+ const risen = toDecibels(rise(toAmplitude(this.#bar), {
213
+ target,
214
+ timeConstantMs
215
+ }, elapsedMs));
216
+ return this.#target - risen < settledDecibels ? this.#target : risen;
217
+ }
109
218
  #schedule(now) {
110
219
  if (this.#frame !== void 0 || !this.isConnected || this.#isSettled(now)) return;
111
220
  if (this.#lastFrame === void 0) this.#lastFrame = now;
112
221
  this.#frame = requestAnimationFrame(this.#tick);
113
222
  }
223
+ #step(now, elapsedMs) {
224
+ const mode = this.ballistics;
225
+ if (mode === "vu") {
226
+ this.#stepNeedle(elapsedMs);
227
+ return;
228
+ }
229
+ const { fallDecibelsPerSecond, riseMs } = ballisticsRates[mode];
230
+ const floor = Math.max(this.#target, this.min);
231
+ this.#bar = this.#target > this.#bar ? this.#risen(riseMs, elapsedMs) : fall(this.#bar, {
232
+ fallDecibelsPerSecond,
233
+ floor
234
+ }, elapsedMs);
235
+ if (this.#bar >= this.#peakHold) {
236
+ this.#peakHold = this.#bar;
237
+ this.#peakHoldAt = now;
238
+ }
239
+ const pastHoldMs = now - this.#peakHoldAt - holdMs;
240
+ if (pastHoldMs >= 0) this.#peakHold = fall(this.#peakHold, {
241
+ fallDecibelsPerSecond,
242
+ floor: this.#bar
243
+ }, Math.min(elapsedMs, pastHoldMs));
244
+ }
245
+ #stepNeedle(elapsedMs) {
246
+ const target = toAmplitude(this.#target);
247
+ const needle = stepNeedle({
248
+ carryMs: this.#carryMs,
249
+ position: toAmplitude(this.#bar),
250
+ velocity: this.#velocity
251
+ }, target, elapsedMs);
252
+ const isLanded = Math.abs(needle.position - target) < settledAmplitude && Math.abs(needle.velocity) < settledPerSecond;
253
+ this.#carryMs = needle.carryMs;
254
+ this.#velocity = isLanded ? 0 : needle.velocity;
255
+ this.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));
256
+ }
114
257
  #tick = (time) => {
115
- const fall = releaseDecibelsPerSecond * Math.max(0, time - (this.#lastFrame ?? time)) / 1e3;
258
+ const elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));
116
259
  this.#frame = void 0;
117
260
  this.#lastFrame = time;
118
- this.#bar = Math.max(this.#target, this.min, this.#bar - fall);
119
- if (time - this.#peakAt >= holdMs) this.#peak = Math.max(this.#bar, this.#peak - fall);
261
+ this.#step(time, elapsedMs);
120
262
  this.#render(time);
121
263
  if (this.#isSettled(time)) {
122
264
  this.#lastFrame = void 0;
@@ -1,6 +1,5 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
- import { SonicRangeElement } from "./range-element.js";
3
- import { readPxProperty } from "../lib/read-px-property.js";
2
+ import { SonicValueElement } from "./value-element.js";
4
3
 
5
4
  //#region src/elements/number.ts
6
5
  const renderNumber = template(`
@@ -16,10 +15,19 @@ const renderNumber = template(`
16
15
  />
17
16
  </div>
18
17
  `, HTMLDivElement);
19
- const fallbackTravelPx = 160;
20
- var SonicNumber = class extends SonicRangeElement {
18
+ var SonicNumber = class extends SonicValueElement {
19
+ get press() {
20
+ return this.getAttribute("press") === "step" ? "step" : "none";
21
+ }
22
+ set press(gesture) {
23
+ this.reflect("press", gesture);
24
+ }
21
25
  #number = renderNumber();
22
26
  #digits = requireChild(this.#number, ".sonic-number-value", HTMLSpanElement);
27
+ connectedCallback() {
28
+ this.upgradeProperties("press");
29
+ super.connectedCallback();
30
+ }
23
31
  connect(signal) {
24
32
  const number = this.#number;
25
33
  this.appendOnce(number);
@@ -27,18 +35,23 @@ var SonicNumber = class extends SonicRangeElement {
27
35
  this.checkStyles(number, "number.css");
28
36
  this.bindGestures(number, signal, () => ({
29
37
  position: (event) => -event.clientY,
30
- travelPx: readPxProperty(getComputedStyle(number), "--_sonic-number-travel", fallbackTravelPx)
38
+ travelPx: this.travelPx(number, "--_sonic-number-travel")
31
39
  }));
32
40
  }
33
41
  controlRole() {
34
42
  return "spinbutton";
35
43
  }
44
+ draw() {
45
+ this.#digits.textContent = this.valueText;
46
+ }
36
47
  focusTarget() {
37
48
  return this.#number;
38
49
  }
39
- render() {
40
- this.#digits.textContent = this.valueText();
41
- this.renderAria(this.#number);
50
+ tapTarget() {
51
+ if (this.press !== "step") return void 0;
52
+ const mapping = this.mapping();
53
+ const next = mapping.keyTarget("ArrowUp", this.value);
54
+ return next === void 0 || next === this.value ? mapping.bounds[0] : next;
42
55
  }
43
56
  };
44
57