@editframe/elements 0.14.0-beta.3 → 0.15.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.js +0 -2
- package/dist/elements/EFAudio.d.ts +0 -1
- package/dist/elements/EFAudio.js +1 -5
- package/dist/elements/EFCaptions.js +1 -1
- package/dist/elements/EFImage.d.ts +2 -1
- package/dist/elements/EFImage.js +9 -3
- package/dist/elements/EFMedia.d.ts +7 -0
- package/dist/elements/EFMedia.js +159 -8
- package/dist/elements/EFTemporal.d.ts +7 -3
- package/dist/elements/EFTemporal.js +19 -1
- package/dist/elements/EFTimegroup.d.ts +1 -5
- package/dist/elements/EFTimegroup.js +5 -6
- package/dist/elements/EFWaveform.d.ts +16 -8
- package/dist/elements/EFWaveform.js +244 -164
- package/dist/elements/TargetController.d.ts +25 -0
- package/dist/elements/TargetController.js +164 -0
- package/dist/elements/TargetController.test.d.ts +19 -0
- package/dist/gui/EFPreview.d.ts +1 -1
- package/dist/gui/EFPreview.js +1 -0
- package/dist/gui/EFWorkbench.js +1 -1
- package/package.json +3 -2
- package/src/elements/EFAudio.ts +1 -4
- package/src/elements/EFCaptions.ts +1 -1
- package/src/elements/EFImage.browsertest.ts +33 -2
- package/src/elements/EFImage.ts +10 -3
- package/src/elements/EFMedia.ts +187 -6
- package/src/elements/EFTemporal.ts +37 -5
- package/src/elements/EFTimegroup.ts +5 -7
- package/src/elements/EFWaveform.ts +302 -204
- package/src/elements/TargetController.test.ts +229 -0
- package/src/elements/TargetController.ts +219 -0
- package/src/gui/EFPreview.ts +10 -9
- package/src/gui/EFWorkbench.ts +1 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { CSSStyleObserver } from "@bramus/style-observer";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
import { LitElement, type PropertyValueMap, css, html } from "lit";
|
|
5
|
-
import { customElement, property } from "lit/decorators.js";
|
|
4
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
6
5
|
import { type Ref, createRef, ref } from "lit/directives/ref.js";
|
|
7
6
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
7
|
+
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
8
8
|
import { TWMixin } from "../gui/TWMixin.js";
|
|
9
9
|
import { CrossUpdateController } from "./CrossUpdateController.js";
|
|
10
|
+
import type { EFAudio } from "./EFAudio.js";
|
|
10
11
|
import { EFTemporal } from "./EFTemporal.js";
|
|
11
|
-
import { EFVideo } from "./EFVideo.js";
|
|
12
|
+
import type { EFVideo } from "./EFVideo.js";
|
|
13
|
+
import { TargetController } from "./TargetController.ts";
|
|
12
14
|
|
|
13
15
|
@customElement("ef-waveform")
|
|
14
16
|
export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
@@ -30,6 +32,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
30
32
|
|
|
31
33
|
canvasRef: Ref<HTMLCanvasElement> = createRef();
|
|
32
34
|
private ctx: CanvasRenderingContext2D | null = null;
|
|
35
|
+
private styleObserver: CSSStyleObserver | null = null;
|
|
33
36
|
|
|
34
37
|
private resizeObserver?: ResizeObserver;
|
|
35
38
|
private mutationObserver?: MutationObserver;
|
|
@@ -42,30 +45,33 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
42
45
|
type: String,
|
|
43
46
|
attribute: "mode",
|
|
44
47
|
})
|
|
45
|
-
mode:
|
|
46
|
-
|
|
47
|
-
| "bars"
|
|
48
|
-
| "bricks"
|
|
49
|
-
| "equalizer"
|
|
50
|
-
| "curve"
|
|
51
|
-
| "line"
|
|
52
|
-
| "pixel"
|
|
53
|
-
| "wave" = "bars";
|
|
48
|
+
mode: "roundBars" | "bars" | "bricks" | "line" | "pixel" | "wave" | "spikes" =
|
|
49
|
+
"bars";
|
|
54
50
|
|
|
55
51
|
@property({ type: String })
|
|
56
52
|
color = "currentColor";
|
|
57
53
|
|
|
58
|
-
@property({ type: String,
|
|
59
|
-
|
|
54
|
+
@property({ type: String, reflect: true })
|
|
55
|
+
target = "";
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
@state()
|
|
58
|
+
targetElement: EFAudio | EFVideo | null = null;
|
|
59
|
+
|
|
60
|
+
@property({ type: Number, attribute: "line-width" })
|
|
61
|
+
lineWidth = 4;
|
|
62
|
+
|
|
63
|
+
targetController: TargetController = new TargetController(this);
|
|
64
64
|
|
|
65
65
|
connectedCallback() {
|
|
66
66
|
super.connectedCallback();
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
try {
|
|
68
|
+
if (this.targetElement) {
|
|
69
|
+
new CrossUpdateController(this.targetElement, this);
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// TODO: determine if this is a critical error, or if we should just ignore it
|
|
73
|
+
// currenty evidence suggests everything still works
|
|
74
|
+
// no target element, no cross update controller
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
// Initialize ResizeObserver
|
|
@@ -87,6 +93,13 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
87
93
|
|
|
88
94
|
// Observe attribute changes on the element
|
|
89
95
|
this.mutationObserver.observe(this, { attributes: true });
|
|
96
|
+
|
|
97
|
+
if (!EF_RENDERING()) {
|
|
98
|
+
this.styleObserver = new CSSStyleObserver(["color"], () => {
|
|
99
|
+
this.frameTask.run();
|
|
100
|
+
});
|
|
101
|
+
this.styleObserver.attach(this);
|
|
102
|
+
}
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
disconnectedCallback() {
|
|
@@ -94,6 +107,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
94
107
|
// Disconnect the observers when the element is removed from the DOM
|
|
95
108
|
this.resizeObserver?.disconnect();
|
|
96
109
|
this.mutationObserver?.disconnect();
|
|
110
|
+
this.styleObserver?.detach();
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
private resizeCanvas() {
|
|
@@ -107,7 +121,10 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
107
121
|
const canvas = this.canvasRef.value;
|
|
108
122
|
if (!canvas) return null;
|
|
109
123
|
|
|
110
|
-
const rect =
|
|
124
|
+
const rect = {
|
|
125
|
+
width: this.offsetWidth,
|
|
126
|
+
height: this.offsetHeight,
|
|
127
|
+
};
|
|
111
128
|
const dpr = window.devicePixelRatio;
|
|
112
129
|
|
|
113
130
|
canvas.style.width = `${rect.width}px`;
|
|
@@ -121,27 +138,33 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
121
138
|
ctx.reset();
|
|
122
139
|
|
|
123
140
|
// Scale all drawing operations by dpr
|
|
124
|
-
ctx.scale(dpr, dpr);
|
|
125
|
-
|
|
126
141
|
return ctx;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
protected drawBars(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
|
|
130
145
|
const canvas = ctx.canvas;
|
|
131
|
-
const waveWidth = canvas.width
|
|
132
|
-
const waveHeight = canvas.height
|
|
133
|
-
|
|
134
|
-
const
|
|
146
|
+
const waveWidth = canvas.width;
|
|
147
|
+
const waveHeight = canvas.height;
|
|
148
|
+
|
|
149
|
+
const totalBars = frequencyData.length;
|
|
150
|
+
const paddingInner = 0.5;
|
|
151
|
+
const paddingOuter = 0.01;
|
|
152
|
+
const availableWidth = waveWidth;
|
|
153
|
+
const barWidth =
|
|
154
|
+
availableWidth / (totalBars + (totalBars - 1) * paddingInner);
|
|
135
155
|
|
|
136
156
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
157
|
+
const path = new Path2D();
|
|
137
158
|
|
|
138
159
|
frequencyData.forEach((value, i) => {
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const y =
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
161
|
+
const barHeight = normalizedValue * waveHeight;
|
|
162
|
+
const y = (waveHeight - barHeight) / 2;
|
|
163
|
+
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
164
|
+
path.rect(x, y, barWidth, barHeight);
|
|
144
165
|
});
|
|
166
|
+
|
|
167
|
+
ctx.fill(path);
|
|
145
168
|
}
|
|
146
169
|
|
|
147
170
|
protected drawBricks(
|
|
@@ -149,55 +172,28 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
149
172
|
frequencyData: Uint8Array,
|
|
150
173
|
) {
|
|
151
174
|
const canvas = ctx.canvas;
|
|
152
|
-
const waveWidth = canvas.width
|
|
153
|
-
const waveHeight = canvas.height
|
|
154
|
-
const brickWidth = waveWidth / frequencyData.length;
|
|
155
|
-
const brickHeightFactor = waveHeight / 255 / 2;
|
|
156
|
-
const brickPadding = 2;
|
|
157
|
-
const midHeight = waveHeight / 2;
|
|
158
|
-
|
|
175
|
+
const waveWidth = canvas.width;
|
|
176
|
+
const waveHeight = canvas.height;
|
|
159
177
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
178
|
+
const path = new Path2D();
|
|
160
179
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
ctx.moveTo(0, midHeight);
|
|
166
|
-
ctx.lineTo(waveWidth, midHeight);
|
|
167
|
-
ctx.stroke();
|
|
168
|
-
ctx.setLineDash([]); // Reset dash
|
|
169
|
-
|
|
170
|
-
// Draw bricks
|
|
171
|
-
frequencyData.forEach((value, i) => {
|
|
172
|
-
const x = i * brickWidth;
|
|
173
|
-
const height = value * brickHeightFactor * 2;
|
|
174
|
-
const y = midHeight - height / 2;
|
|
175
|
-
|
|
176
|
-
ctx.fillRect(x, y, brickWidth - brickPadding, height);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
|
|
181
|
-
const canvas = ctx.canvas;
|
|
182
|
-
const waveWidth = canvas.width / devicePixelRatio;
|
|
183
|
-
const waveHeight = canvas.height / devicePixelRatio;
|
|
184
|
-
|
|
185
|
-
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
186
|
-
ctx.beginPath();
|
|
180
|
+
const columnWidth = waveWidth / frequencyData.length;
|
|
181
|
+
const boxSize = columnWidth * 0.9;
|
|
182
|
+
const verticalGap = boxSize * 0.2; // Add spacing between bricks
|
|
183
|
+
const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap)); // Account for gaps in height calculation
|
|
187
184
|
|
|
188
185
|
frequencyData.forEach((value, i) => {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
186
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
187
|
+
const brickCount = Math.floor(normalizedValue * maxBricks);
|
|
191
188
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
for (let j = 0; j < brickCount; j++) {
|
|
190
|
+
const x = columnWidth * i;
|
|
191
|
+
const y = waveHeight - (j + 1) * (boxSize + verticalGap); // Include gap in position calculation
|
|
192
|
+
path.rect(x, y, boxSize, boxSize);
|
|
196
193
|
}
|
|
197
194
|
});
|
|
198
195
|
|
|
199
|
-
ctx.
|
|
200
|
-
ctx.stroke();
|
|
196
|
+
ctx.fill(path);
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
protected drawRoundBars(
|
|
@@ -205,25 +201,34 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
205
201
|
frequencyData: Uint8Array,
|
|
206
202
|
) {
|
|
207
203
|
const canvas = ctx.canvas;
|
|
208
|
-
const waveWidth = canvas.width
|
|
209
|
-
const waveHeight = canvas.height
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
204
|
+
const waveWidth = canvas.width;
|
|
205
|
+
const waveHeight = canvas.height;
|
|
206
|
+
|
|
207
|
+
// Similar padding calculation as drawBars
|
|
208
|
+
const totalBars = frequencyData.length;
|
|
209
|
+
const paddingInner = 0.5;
|
|
210
|
+
const paddingOuter = 0.01;
|
|
211
|
+
const availableWidth = waveWidth;
|
|
212
|
+
const barWidth =
|
|
213
|
+
availableWidth / (totalBars + (totalBars - 1) * paddingInner);
|
|
213
214
|
|
|
214
215
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
215
216
|
|
|
217
|
+
// Create a single Path2D object for all rounded bars
|
|
218
|
+
const path = new Path2D();
|
|
219
|
+
|
|
216
220
|
frequencyData.forEach((value, i) => {
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
const
|
|
221
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
222
|
+
const height = normalizedValue * waveHeight; // Use full wave height like in drawBars
|
|
223
|
+
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
224
|
+
const y = (waveHeight - height) / 2; // Center vertically
|
|
220
225
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
ctx.beginPath();
|
|
224
|
-
ctx.roundRect(x, y, barWidth, Math.max(height * 2, 2), radius);
|
|
225
|
-
ctx.fill();
|
|
226
|
+
// Add rounded rectangle to path
|
|
227
|
+
path.roundRect(x, y, barWidth, height, barWidth / 2);
|
|
226
228
|
});
|
|
229
|
+
|
|
230
|
+
// Single fill operation for all bars
|
|
231
|
+
ctx.fill(path);
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
protected drawEqualizer(
|
|
@@ -231,54 +236,60 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
231
236
|
frequencyData: Uint8Array,
|
|
232
237
|
) {
|
|
233
238
|
const canvas = ctx.canvas;
|
|
234
|
-
const waveWidth = canvas.width
|
|
235
|
-
const waveHeight = canvas.height
|
|
236
|
-
const barWidth = (waveWidth / frequencyData.length) * 0.8;
|
|
239
|
+
const waveWidth = canvas.width;
|
|
240
|
+
const waveHeight = canvas.height;
|
|
237
241
|
const baseline = waveHeight / 2;
|
|
242
|
+
const barWidth = (waveWidth / frequencyData.length) * 0.8;
|
|
238
243
|
|
|
239
244
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
240
245
|
|
|
246
|
+
// Create paths for baseline and bars
|
|
247
|
+
const baselinePath = new Path2D();
|
|
248
|
+
const barsPath = new Path2D();
|
|
249
|
+
|
|
241
250
|
// Draw baseline
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ctx.moveTo(0, baseline);
|
|
245
|
-
ctx.lineTo(waveWidth, baseline);
|
|
246
|
-
ctx.stroke();
|
|
251
|
+
baselinePath.moveTo(0, baseline);
|
|
252
|
+
baselinePath.lineTo(waveWidth, baseline);
|
|
247
253
|
|
|
248
254
|
// Draw bars
|
|
249
255
|
frequencyData.forEach((value, i) => {
|
|
250
256
|
const height = (value / 255) * (waveHeight / 2);
|
|
251
257
|
const x = i * (waveWidth / frequencyData.length);
|
|
252
258
|
const y = baseline - height;
|
|
253
|
-
|
|
254
|
-
ctx.fillRect(x, y, barWidth, Math.max(height * 2, 1));
|
|
259
|
+
barsPath.rect(x, y, barWidth, Math.max(height * 2, 1));
|
|
255
260
|
});
|
|
261
|
+
|
|
262
|
+
// Render baseline
|
|
263
|
+
ctx.lineWidth = 2;
|
|
264
|
+
ctx.stroke(baselinePath);
|
|
265
|
+
|
|
266
|
+
// Render bars
|
|
267
|
+
ctx.fill(barsPath);
|
|
256
268
|
}
|
|
257
269
|
|
|
258
|
-
protected
|
|
259
|
-
ctx: CanvasRenderingContext2D,
|
|
260
|
-
frequencyData: Uint8Array,
|
|
261
|
-
) {
|
|
270
|
+
protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
|
|
262
271
|
const canvas = ctx.canvas;
|
|
263
|
-
const waveWidth = canvas.width
|
|
264
|
-
const waveHeight = canvas.height
|
|
272
|
+
const waveWidth = canvas.width;
|
|
273
|
+
const waveHeight = canvas.height;
|
|
265
274
|
|
|
266
275
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
267
|
-
|
|
276
|
+
|
|
277
|
+
// Create a single Path2D object for the curve
|
|
278
|
+
const path = new Path2D();
|
|
268
279
|
|
|
269
280
|
frequencyData.forEach((value, i) => {
|
|
270
281
|
const x = (i / frequencyData.length) * waveWidth;
|
|
271
282
|
const y = (1 - value / 255) * waveHeight;
|
|
272
283
|
|
|
273
284
|
if (i === 0) {
|
|
274
|
-
|
|
285
|
+
path.moveTo(x, y);
|
|
275
286
|
} else {
|
|
276
|
-
|
|
287
|
+
path.lineTo(x, y);
|
|
277
288
|
}
|
|
278
289
|
});
|
|
279
290
|
|
|
280
|
-
ctx.lineWidth =
|
|
281
|
-
ctx.stroke();
|
|
291
|
+
ctx.lineWidth = this.lineWidth;
|
|
292
|
+
ctx.stroke(path);
|
|
282
293
|
}
|
|
283
294
|
|
|
284
295
|
protected drawPixel(
|
|
@@ -286,143 +297,224 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
286
297
|
frequencyData: Uint8Array,
|
|
287
298
|
) {
|
|
288
299
|
const canvas = ctx.canvas;
|
|
289
|
-
const waveWidth = canvas.width
|
|
290
|
-
const waveHeight = canvas.height
|
|
300
|
+
const waveWidth = canvas.width;
|
|
301
|
+
const waveHeight = canvas.height;
|
|
291
302
|
const baseline = waveHeight / 2;
|
|
292
|
-
const barWidth =
|
|
303
|
+
const barWidth = waveWidth / frequencyData.length;
|
|
293
304
|
|
|
294
305
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
306
|
+
const path = new Path2D();
|
|
295
307
|
|
|
296
308
|
frequencyData.forEach((value, i) => {
|
|
309
|
+
const normalizedValue = Math.min((value / 255) * 2, 1); // Updated normalization
|
|
297
310
|
const x = i * (waveWidth / frequencyData.length);
|
|
298
|
-
const barHeight = (
|
|
311
|
+
const barHeight = normalizedValue * (waveHeight / 2); // Half height since we extend both ways
|
|
299
312
|
const y = baseline - barHeight;
|
|
300
|
-
|
|
301
|
-
ctx.fillRect(x, y, barWidth, barHeight * 2);
|
|
313
|
+
path.rect(x, y, barWidth, barHeight * 2); // Double height to extend both ways
|
|
302
314
|
});
|
|
315
|
+
|
|
316
|
+
ctx.fill(path);
|
|
303
317
|
}
|
|
304
318
|
|
|
305
319
|
protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
|
|
306
320
|
const canvas = ctx.canvas;
|
|
307
|
-
const waveWidth = canvas.width
|
|
308
|
-
const waveHeight = canvas.height
|
|
309
|
-
const
|
|
310
|
-
const
|
|
321
|
+
const waveWidth = canvas.width;
|
|
322
|
+
const waveHeight = canvas.height;
|
|
323
|
+
const paddingOuter = 0.01;
|
|
324
|
+
const availableWidth = waveWidth * (1 - 2 * paddingOuter);
|
|
325
|
+
const startX = waveWidth * paddingOuter;
|
|
311
326
|
|
|
312
327
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
328
|
+
const path = new Path2D();
|
|
313
329
|
|
|
314
|
-
// Draw
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
ctx.strokeStyle = this.color;
|
|
319
|
-
ctx.lineWidth = 1;
|
|
320
|
-
ctx.stroke();
|
|
330
|
+
// Draw top curve
|
|
331
|
+
const firstValue = Math.min(((frequencyData[0] ?? 0) / 255) * 2, 1);
|
|
332
|
+
const firstY = (waveHeight - firstValue * waveHeight) / 2;
|
|
333
|
+
path.moveTo(startX, firstY);
|
|
321
334
|
|
|
322
|
-
// Draw
|
|
335
|
+
// Draw top half
|
|
323
336
|
frequencyData.forEach((value, i) => {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const
|
|
337
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
338
|
+
const x = startX + (i / (frequencyData.length - 1)) * availableWidth;
|
|
339
|
+
const barHeight = normalizedValue * waveHeight;
|
|
340
|
+
const y = (waveHeight - barHeight) / 2;
|
|
327
341
|
|
|
328
|
-
|
|
342
|
+
if (i === 0) {
|
|
343
|
+
path.moveTo(x, y);
|
|
344
|
+
} else {
|
|
345
|
+
const prevX =
|
|
346
|
+
startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;
|
|
347
|
+
const prevValue = Math.min(((frequencyData[i - 1] ?? 0) / 255) * 2, 1);
|
|
348
|
+
const prevBarHeight = prevValue * waveHeight;
|
|
349
|
+
const prevY = (waveHeight - prevBarHeight) / 2;
|
|
350
|
+
const xc = (prevX + x) / 2;
|
|
351
|
+
const yc = (prevY + y) / 2;
|
|
352
|
+
path.quadraticCurveTo(prevX, prevY, xc, yc);
|
|
353
|
+
}
|
|
329
354
|
});
|
|
355
|
+
|
|
356
|
+
// Draw bottom half
|
|
357
|
+
for (let i = frequencyData.length - 1; i >= 0; i--) {
|
|
358
|
+
const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);
|
|
359
|
+
const x = startX + (i / (frequencyData.length - 1)) * availableWidth;
|
|
360
|
+
const barHeight = normalizedValue * waveHeight;
|
|
361
|
+
const y = (waveHeight + barHeight) / 2;
|
|
362
|
+
|
|
363
|
+
if (i === frequencyData.length - 1) {
|
|
364
|
+
path.lineTo(x, y);
|
|
365
|
+
} else {
|
|
366
|
+
const nextX =
|
|
367
|
+
startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;
|
|
368
|
+
const nextValue = Math.min(((frequencyData[i + 1] ?? 0) / 255) * 2, 1);
|
|
369
|
+
const nextBarHeight = nextValue * waveHeight;
|
|
370
|
+
const nextY = (waveHeight + nextBarHeight) / 2;
|
|
371
|
+
const xc = (nextX + x) / 2;
|
|
372
|
+
const yc = (nextY + y) / 2;
|
|
373
|
+
path.quadraticCurveTo(nextX, nextY, xc, yc);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Close the path with a smooth curve back to start
|
|
378
|
+
const lastY = (waveHeight + firstValue * waveHeight) / 2;
|
|
379
|
+
const controlX = startX;
|
|
380
|
+
const controlY = (lastY + firstY) / 2;
|
|
381
|
+
path.quadraticCurveTo(controlX, controlY, startX, firstY);
|
|
382
|
+
|
|
383
|
+
ctx.fill(path);
|
|
330
384
|
}
|
|
331
385
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
386
|
+
protected drawSpikes(
|
|
387
|
+
ctx: CanvasRenderingContext2D,
|
|
388
|
+
frequencyData: Uint8Array,
|
|
389
|
+
) {
|
|
390
|
+
const canvas = ctx.canvas;
|
|
391
|
+
const waveWidth = canvas.width;
|
|
392
|
+
const waveHeight = canvas.height;
|
|
393
|
+
const paddingOuter = 0.01;
|
|
394
|
+
const availableWidth = waveWidth * (1 - 2 * paddingOuter);
|
|
395
|
+
const startX = waveWidth * paddingOuter;
|
|
342
396
|
|
|
343
|
-
|
|
397
|
+
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
398
|
+
const path = new Path2D();
|
|
399
|
+
|
|
400
|
+
// Draw top curve
|
|
401
|
+
const firstValue = (frequencyData[0] ?? 0) / 255;
|
|
402
|
+
const firstY = (waveHeight - firstValue * waveHeight) / 2;
|
|
403
|
+
path.moveTo(startX, firstY);
|
|
344
404
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
405
|
+
// Draw top half
|
|
406
|
+
frequencyData.forEach((value, i) => {
|
|
407
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
408
|
+
const x = startX + (i / (frequencyData.length - 1)) * availableWidth;
|
|
409
|
+
const barHeight = normalizedValue * (waveHeight / 2);
|
|
410
|
+
const y = (waveHeight - barHeight * 2) / 2;
|
|
349
411
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
412
|
+
if (i === 0) {
|
|
413
|
+
path.moveTo(x, y);
|
|
414
|
+
} else {
|
|
415
|
+
const prevX =
|
|
416
|
+
startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;
|
|
417
|
+
const prevValue = (frequencyData[i - 1] ?? 0) / 255;
|
|
418
|
+
const prevBarHeight = prevValue * (waveHeight / 2);
|
|
419
|
+
const prevY = (waveHeight - prevBarHeight * 2) / 2;
|
|
420
|
+
const xc = (prevX + x) / 2;
|
|
421
|
+
const yc = (prevY + y) / 2;
|
|
422
|
+
path.quadraticCurveTo(prevX, prevY, xc, yc);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
355
425
|
|
|
356
|
-
|
|
357
|
-
|
|
426
|
+
// Draw bottom half
|
|
427
|
+
for (let i = frequencyData.length - 1; i >= 0; i--) {
|
|
428
|
+
const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);
|
|
429
|
+
const x = startX + (i / (frequencyData.length - 1)) * availableWidth;
|
|
430
|
+
const barHeight = normalizedValue * (waveHeight / 2);
|
|
431
|
+
const y = (waveHeight + barHeight * 2) / 2;
|
|
358
432
|
|
|
359
|
-
|
|
433
|
+
if (i === frequencyData.length - 1) {
|
|
434
|
+
path.lineTo(x, y);
|
|
435
|
+
} else {
|
|
436
|
+
const nextX =
|
|
437
|
+
startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;
|
|
438
|
+
const nextValue = (frequencyData[i + 1] ?? 0) / 255;
|
|
439
|
+
const nextBarHeight = nextValue * (waveHeight / 2);
|
|
440
|
+
const nextY = (waveHeight + nextBarHeight * 2) / 2;
|
|
441
|
+
const xc = (nextX + x) / 2;
|
|
442
|
+
const yc = (nextY + y) / 2;
|
|
443
|
+
path.quadraticCurveTo(nextX, nextY, xc, yc);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
360
446
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
);
|
|
447
|
+
// Close the path with a smooth curve
|
|
448
|
+
const lastY = (waveHeight + firstValue * waveHeight) / 2;
|
|
449
|
+
const controlX = startX;
|
|
450
|
+
const controlY = (lastY + firstY) / 2;
|
|
451
|
+
path.quadraticCurveTo(controlX, controlY, startX, firstY);
|
|
367
452
|
|
|
368
|
-
|
|
369
|
-
|
|
453
|
+
ctx.fill(path);
|
|
454
|
+
}
|
|
370
455
|
|
|
371
|
-
|
|
372
|
-
|
|
456
|
+
frameTask = new Task(this, {
|
|
457
|
+
autoRun: EF_INTERACTIVE,
|
|
458
|
+
args: () => {
|
|
459
|
+
return [
|
|
460
|
+
this.targetElement,
|
|
461
|
+
this.targetElement?.frequencyDataTask.value,
|
|
462
|
+
] as const;
|
|
463
|
+
},
|
|
464
|
+
task: async () => {
|
|
465
|
+
if (!this.targetElement) return;
|
|
466
|
+
await this.targetElement.frequencyDataTask.taskComplete;
|
|
467
|
+
this.ctx ||= this.initCanvas();
|
|
468
|
+
const ctx = this.ctx;
|
|
469
|
+
if (!ctx) return;
|
|
373
470
|
|
|
374
|
-
|
|
471
|
+
const frequencyData = this.targetElement.frequencyDataTask.value;
|
|
472
|
+
if (!frequencyData) return;
|
|
375
473
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
474
|
+
ctx.save();
|
|
475
|
+
if (this.color === "currentColor") {
|
|
476
|
+
const computedStyle = getComputedStyle(this);
|
|
477
|
+
const currentColor = computedStyle.color;
|
|
478
|
+
ctx.strokeStyle = currentColor;
|
|
479
|
+
ctx.fillStyle = currentColor;
|
|
480
|
+
} else {
|
|
481
|
+
ctx.strokeStyle = this.color;
|
|
482
|
+
ctx.fillStyle = this.color;
|
|
483
|
+
}
|
|
383
484
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
case "equalizer":
|
|
407
|
-
this.drawEqualizer(ctx, smoothedData);
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
485
|
+
switch (this.mode) {
|
|
486
|
+
case "bars":
|
|
487
|
+
this.drawBars(ctx, frequencyData);
|
|
488
|
+
break;
|
|
489
|
+
case "bricks":
|
|
490
|
+
this.drawBricks(ctx, frequencyData);
|
|
491
|
+
break;
|
|
492
|
+
case "line":
|
|
493
|
+
this.drawLine(ctx, frequencyData);
|
|
494
|
+
break;
|
|
495
|
+
case "pixel":
|
|
496
|
+
this.drawPixel(ctx, frequencyData);
|
|
497
|
+
break;
|
|
498
|
+
case "wave":
|
|
499
|
+
this.drawWave(ctx, frequencyData);
|
|
500
|
+
break;
|
|
501
|
+
case "spikes":
|
|
502
|
+
this.drawSpikes(ctx, frequencyData);
|
|
503
|
+
break;
|
|
504
|
+
case "roundBars":
|
|
505
|
+
this.drawRoundBars(ctx, frequencyData);
|
|
506
|
+
break;
|
|
410
507
|
}
|
|
508
|
+
|
|
509
|
+
ctx.restore();
|
|
411
510
|
},
|
|
412
511
|
});
|
|
413
512
|
|
|
414
513
|
get durationMs() {
|
|
514
|
+
if (!this.targetElement) return 0;
|
|
415
515
|
return this.targetElement.durationMs;
|
|
416
516
|
}
|
|
417
517
|
|
|
418
|
-
get targetElement() {
|
|
419
|
-
const target = document.getElementById(this.targetSelector ?? "");
|
|
420
|
-
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
421
|
-
return target;
|
|
422
|
-
}
|
|
423
|
-
throw new Error("Invalid target, must be an EFAudio or EFVideo element");
|
|
424
|
-
}
|
|
425
|
-
|
|
426
518
|
protected updated(changedProperties: PropertyValueMap<this>): void {
|
|
427
519
|
super.updated(changedProperties);
|
|
428
520
|
|
|
@@ -433,3 +525,9 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
433
525
|
}
|
|
434
526
|
}
|
|
435
527
|
}
|
|
528
|
+
|
|
529
|
+
declare global {
|
|
530
|
+
interface HTMLElementTagNameMap {
|
|
531
|
+
"ef-waveform": EFWaveform & Element;
|
|
532
|
+
}
|
|
533
|
+
}
|