@editframe/elements 0.15.0-beta.17 → 0.15.0-beta.5
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/elements/EFMedia.d.ts +5 -7
- package/dist/elements/EFMedia.js +19 -174
- package/dist/elements/EFTimegroup.d.ts +1 -1
- package/dist/elements/EFWaveform.d.ts +2 -2
- package/dist/elements/EFWaveform.js +24 -33
- package/dist/gui/TWMixin.css.js +1 -1
- package/dist/gui/TWMixin.js +2 -14
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -4
- package/dist/style.css +0 -3
- package/dist/types.json +1 -0
- package/package.json +5 -10
- package/src/elements/EFMedia.ts +15 -236
- package/src/elements/EFWaveform.ts +37 -49
- package/src/gui/TWMixin.ts +2 -19
- package/dist/getRenderInfo.d.ts +0 -51
- package/dist/getRenderInfo.js +0 -72
- package/types.json +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.15.0-beta.
|
|
3
|
+
"version": "0.15.0-beta.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -10,31 +10,26 @@
|
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"./styles.css": "./dist/style.css",
|
|
13
|
-
"./types.json":
|
|
14
|
-
"import": {
|
|
15
|
-
"default": "./types.json"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
13
|
+
"./types.json": "./dist/types.json"
|
|
18
14
|
},
|
|
19
15
|
"type": "module",
|
|
20
16
|
"scripts": {
|
|
21
17
|
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
|
|
22
18
|
"build": "vite build",
|
|
23
19
|
"build:watch": "vite build --watch",
|
|
24
|
-
"typedoc": "typedoc --json ./types.json --plugin typedoc-plugin-zod --excludeExternals ./src && jq -c . ./types.json > ./types.tmp.json && mv ./types.tmp.json ./types.json"
|
|
20
|
+
"typedoc": "typedoc --json ./dist/types.json --plugin typedoc-plugin-zod --excludeExternals ./src && jq -c . ./dist/types.json > ./dist/types.tmp.json && mv ./dist/types.tmp.json ./dist/types.json"
|
|
25
21
|
},
|
|
26
22
|
"author": "",
|
|
27
23
|
"license": "UNLICENSED",
|
|
28
24
|
"dependencies": {
|
|
29
25
|
"@bramus/style-observer": "^1.3.0",
|
|
30
|
-
"@editframe/assets": "0.15.0-beta.
|
|
26
|
+
"@editframe/assets": "0.15.0-beta.5",
|
|
31
27
|
"@lit/context": "^1.1.2",
|
|
32
28
|
"@lit/task": "^1.0.1",
|
|
33
29
|
"d3": "^7.9.0",
|
|
34
30
|
"debug": "^4.3.5",
|
|
35
31
|
"lit": "^3.1.4",
|
|
36
|
-
"mp4box": "^0.5.2"
|
|
37
|
-
"zod": "^3.24.1"
|
|
32
|
+
"mp4box": "^0.5.2"
|
|
38
33
|
},
|
|
39
34
|
"devDependencies": {
|
|
40
35
|
"@types/d3": "^7.4.3",
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -126,14 +126,8 @@ export class EFMedia extends EFTargetable(
|
|
|
126
126
|
public trackFragmentIndexLoader = new Task(this, {
|
|
127
127
|
args: () => [this.fragmentIndexPath(), this.fetch] as const,
|
|
128
128
|
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return (await response.json()) as Record<number, TrackFragmentIndex>;
|
|
133
|
-
} catch (error) {
|
|
134
|
-
log("Failed to load track fragment index", error);
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
129
|
+
const response = await fetch(fragmentIndexPath, { signal });
|
|
130
|
+
return (await response.json()) as Record<number, TrackFragmentIndex>;
|
|
137
131
|
},
|
|
138
132
|
onComplete: () => {
|
|
139
133
|
this.requestUpdate("ownCurrentTimeMs");
|
|
@@ -590,33 +584,14 @@ export class EFMedia extends EFTargetable(
|
|
|
590
584
|
};
|
|
591
585
|
}
|
|
592
586
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
this.setAttribute("fft-size", String(value));
|
|
596
|
-
this.requestUpdate("fft-size", oldValue);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
set fftDecay(value: number) {
|
|
600
|
-
const oldValue = this.fftDecay;
|
|
601
|
-
this.setAttribute("fft-decay", String(value));
|
|
602
|
-
this.requestUpdate("fft-decay", oldValue);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
get fftSize() {
|
|
606
|
-
return Number.parseInt(this.getAttribute("fft-size") ?? "128", 10);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
get fftDecay() {
|
|
610
|
-
return Number.parseInt(this.getAttribute("fft-decay") ?? "8", 10);
|
|
611
|
-
}
|
|
587
|
+
@property({ type: Number })
|
|
588
|
+
fftSize = 512; // Default FFT size
|
|
612
589
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
return this.getAttribute("interpolate-frequencies") !== "false";
|
|
616
|
-
}
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
590
|
+
@property({ type: Number })
|
|
591
|
+
fftDecay = 8; // Default number of frames to analyze
|
|
619
592
|
|
|
593
|
+
private static readonly MIN_DB = -90;
|
|
594
|
+
private static readonly MAX_DB = -20;
|
|
620
595
|
private static readonly DECAY_WEIGHT = 0.7;
|
|
621
596
|
|
|
622
597
|
// Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
|
|
@@ -641,130 +616,6 @@ export class EFMedia extends EFTargetable(
|
|
|
641
616
|
return weights;
|
|
642
617
|
}
|
|
643
618
|
|
|
644
|
-
#byteTimeDomainCache = new LRUCache<string, Uint8Array>(100);
|
|
645
|
-
|
|
646
|
-
byteTimeDomainTask = new Task(this, {
|
|
647
|
-
autoRun: EF_INTERACTIVE,
|
|
648
|
-
args: () =>
|
|
649
|
-
[
|
|
650
|
-
this.audioBufferTask.status,
|
|
651
|
-
this.currentSourceTimeMs,
|
|
652
|
-
this.fftSize,
|
|
653
|
-
this.fftDecay,
|
|
654
|
-
] as const,
|
|
655
|
-
task: async () => {
|
|
656
|
-
await this.audioBufferTask.taskComplete;
|
|
657
|
-
if (!this.audioBufferTask.value) return null;
|
|
658
|
-
if (this.currentSourceTimeMs <= 0) return null;
|
|
659
|
-
|
|
660
|
-
const currentTimeMs = this.currentSourceTimeMs;
|
|
661
|
-
const startOffsetMs = this.audioBufferTask.value.startOffsetMs;
|
|
662
|
-
const audioBuffer = this.audioBufferTask.value.buffer;
|
|
663
|
-
|
|
664
|
-
const smoothedKey = `${this.fftSize}:${this.fftDecay}:${startOffsetMs}:${currentTimeMs}`;
|
|
665
|
-
const cachedData = this.#byteTimeDomainCache.get(smoothedKey);
|
|
666
|
-
if (cachedData) return cachedData;
|
|
667
|
-
|
|
668
|
-
// Process multiple frames with decay, similar to the reference code
|
|
669
|
-
const framesData = await Promise.all(
|
|
670
|
-
Array.from({ length: this.fftDecay }, async (_, frameIndex) => {
|
|
671
|
-
const frameOffset = frameIndex * (1000 / 30);
|
|
672
|
-
const startTime = Math.max(
|
|
673
|
-
0,
|
|
674
|
-
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
675
|
-
);
|
|
676
|
-
|
|
677
|
-
const cacheKey = `${this.fftSize}:${startOffsetMs}:${startTime}`;
|
|
678
|
-
const cachedFrame = this.#byteTimeDomainCache.get(cacheKey);
|
|
679
|
-
if (cachedFrame) return cachedFrame;
|
|
680
|
-
|
|
681
|
-
const audioContext = new OfflineAudioContext(
|
|
682
|
-
2,
|
|
683
|
-
48000 * (1 / 30),
|
|
684
|
-
48000,
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
const source = audioContext.createBufferSource();
|
|
688
|
-
source.buffer = audioBuffer;
|
|
689
|
-
|
|
690
|
-
// Create analyzer for PCM data
|
|
691
|
-
const analyser = audioContext.createAnalyser();
|
|
692
|
-
analyser.fftSize = this.fftSize; // Ensure enough samples
|
|
693
|
-
analyser.minDecibels = -90;
|
|
694
|
-
analyser.maxDecibels = -20;
|
|
695
|
-
|
|
696
|
-
const gainNode = audioContext.createGain();
|
|
697
|
-
gainNode.gain.value = 2.0; // Amplify the signal
|
|
698
|
-
|
|
699
|
-
source.connect(gainNode);
|
|
700
|
-
gainNode.connect(analyser);
|
|
701
|
-
analyser.connect(audioContext.destination);
|
|
702
|
-
|
|
703
|
-
source.start(0, startTime, 1 / 30);
|
|
704
|
-
|
|
705
|
-
const dataLength = analyser.fftSize / 2;
|
|
706
|
-
try {
|
|
707
|
-
await audioContext.startRendering();
|
|
708
|
-
const frameData = new Uint8Array(dataLength);
|
|
709
|
-
analyser.getByteTimeDomainData(frameData);
|
|
710
|
-
|
|
711
|
-
// const points = frameData;
|
|
712
|
-
// Calculate RMS and midpoint values
|
|
713
|
-
const points = new Uint8Array(dataLength);
|
|
714
|
-
for (let i = 0; i < dataLength; i++) {
|
|
715
|
-
const pointSamples = frameData.slice(
|
|
716
|
-
i * (frameData.length / dataLength),
|
|
717
|
-
(i + 1) * (frameData.length / dataLength),
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
// Calculate RMS while preserving sign
|
|
721
|
-
const rms = Math.sqrt(
|
|
722
|
-
pointSamples.reduce((sum, sample) => {
|
|
723
|
-
const normalized = (sample - 128) / 128;
|
|
724
|
-
return sum + normalized * normalized;
|
|
725
|
-
}, 0) / pointSamples.length,
|
|
726
|
-
);
|
|
727
|
-
|
|
728
|
-
// Get average sign of the samples to determine direction
|
|
729
|
-
const avgSign = Math.sign(
|
|
730
|
-
pointSamples.reduce((sum, sample) => sum + (sample - 128), 0),
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
// Convert RMS back to byte range, preserving direction
|
|
734
|
-
points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
this.#byteTimeDomainCache.set(cacheKey, points);
|
|
738
|
-
return points;
|
|
739
|
-
} finally {
|
|
740
|
-
source.disconnect();
|
|
741
|
-
analyser.disconnect();
|
|
742
|
-
}
|
|
743
|
-
}),
|
|
744
|
-
);
|
|
745
|
-
|
|
746
|
-
// Combine frames with decay weighting
|
|
747
|
-
const frameLength = framesData[0]?.length ?? 0;
|
|
748
|
-
const smoothedData = new Uint8Array(frameLength);
|
|
749
|
-
|
|
750
|
-
for (let i = 0; i < frameLength; i++) {
|
|
751
|
-
let weightedSum = 0;
|
|
752
|
-
let weightSum = 0;
|
|
753
|
-
|
|
754
|
-
framesData.forEach((frame, frameIndex) => {
|
|
755
|
-
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
756
|
-
weightedSum += (frame[i] ?? 0) * decayWeight;
|
|
757
|
-
weightSum += decayWeight;
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
this.#byteTimeDomainCache.set(smoothedKey, smoothedData);
|
|
764
|
-
return smoothedData;
|
|
765
|
-
},
|
|
766
|
-
});
|
|
767
|
-
|
|
768
619
|
#frequencyDataCache = new LRUCache<string, Uint8Array>(100);
|
|
769
620
|
|
|
770
621
|
frequencyDataTask = new Task(this, {
|
|
@@ -815,23 +666,13 @@ export class EFMedia extends EFTargetable(
|
|
|
815
666
|
);
|
|
816
667
|
const analyser = audioContext.createAnalyser();
|
|
817
668
|
analyser.fftSize = this.fftSize;
|
|
818
|
-
analyser.minDecibels =
|
|
819
|
-
analyser.maxDecibels =
|
|
820
|
-
|
|
821
|
-
const gainNode = audioContext.createGain();
|
|
822
|
-
gainNode.gain.value = 3.0;
|
|
823
|
-
|
|
824
|
-
const filter = audioContext.createBiquadFilter();
|
|
825
|
-
filter.type = "bandpass";
|
|
826
|
-
filter.frequency.value = 15000;
|
|
827
|
-
filter.Q.value = 0.05;
|
|
669
|
+
analyser.minDecibels = EFMedia.MIN_DB;
|
|
670
|
+
analyser.maxDecibels = EFMedia.MAX_DB;
|
|
828
671
|
|
|
829
672
|
const audioBufferSource = audioContext.createBufferSource();
|
|
830
673
|
audioBufferSource.buffer = audioBuffer;
|
|
831
674
|
|
|
832
|
-
audioBufferSource.connect(
|
|
833
|
-
filter.connect(gainNode);
|
|
834
|
-
gainNode.connect(analyser);
|
|
675
|
+
audioBufferSource.connect(analyser);
|
|
835
676
|
analyser.connect(audioContext.destination);
|
|
836
677
|
|
|
837
678
|
audioBufferSource.start(0, startTime, 1 / 30);
|
|
@@ -861,7 +702,7 @@ export class EFMedia extends EFTargetable(
|
|
|
861
702
|
|
|
862
703
|
framesData.forEach((frame, frameIndex) => {
|
|
863
704
|
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
864
|
-
// biome-ignore lint/style/noNonNullAssertion:
|
|
705
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to forEach
|
|
865
706
|
weightedSum += frame[i]! * decayWeight;
|
|
866
707
|
weightSum += decayWeight;
|
|
867
708
|
});
|
|
@@ -871,7 +712,7 @@ export class EFMedia extends EFTargetable(
|
|
|
871
712
|
|
|
872
713
|
// Apply frequency weights using instance FREQ_WEIGHTS
|
|
873
714
|
smoothedData.forEach((value, i) => {
|
|
874
|
-
// biome-ignore lint/style/noNonNullAssertion:
|
|
715
|
+
// biome-ignore lint/style/noNonNullAssertion: Will exist due to forEach
|
|
875
716
|
const freqWeight = this.FREQ_WEIGHTS[i]!;
|
|
876
717
|
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
877
718
|
});
|
|
@@ -882,70 +723,8 @@ export class EFMedia extends EFTargetable(
|
|
|
882
723
|
0,
|
|
883
724
|
Math.floor(smoothedData.length / 2),
|
|
884
725
|
);
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
: slicedData;
|
|
888
|
-
this.#frequencyDataCache.set(smoothedKey, processedData);
|
|
889
|
-
return processedData;
|
|
726
|
+
this.#frequencyDataCache.set(smoothedKey, slicedData);
|
|
727
|
+
return slicedData;
|
|
890
728
|
},
|
|
891
729
|
});
|
|
892
730
|
}
|
|
893
|
-
|
|
894
|
-
function processFFTData(fftData: Uint8Array, zeroThresholdPercent = 0.1) {
|
|
895
|
-
// Step 1: Determine the threshold for zeros
|
|
896
|
-
const totalBins = fftData.length;
|
|
897
|
-
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
898
|
-
|
|
899
|
-
// Step 2: Interrogate the FFT output to find the cutoff point
|
|
900
|
-
let zeroCount = 0;
|
|
901
|
-
let cutoffIndex = totalBins; // Default to the end of the array
|
|
902
|
-
|
|
903
|
-
for (let i = totalBins - 1; i >= 0; i--) {
|
|
904
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
905
|
-
if (fftData[i]! < 10) {
|
|
906
|
-
zeroCount++;
|
|
907
|
-
} else {
|
|
908
|
-
// If we encounter a non-zero value, we can stop
|
|
909
|
-
if (zeroCount >= zeroThresholdCount) {
|
|
910
|
-
cutoffIndex = i + 1; // Include this index
|
|
911
|
-
break;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
if (cutoffIndex < zeroThresholdCount) {
|
|
917
|
-
return fftData;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// Step 3: Resample the "good" portion of the data
|
|
921
|
-
const goodData = fftData.slice(0, cutoffIndex);
|
|
922
|
-
const resampledData = interpolateData(goodData, fftData.length);
|
|
923
|
-
|
|
924
|
-
return resampledData;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function interpolateData(data: Uint8Array, targetSize: number) {
|
|
928
|
-
const resampled = new Uint8Array(targetSize);
|
|
929
|
-
const dataLength = data.length;
|
|
930
|
-
|
|
931
|
-
for (let i = 0; i < targetSize; i++) {
|
|
932
|
-
// Calculate the corresponding index in the original data
|
|
933
|
-
const ratio = (i / (targetSize - 1)) * (dataLength - 1);
|
|
934
|
-
const index = Math.floor(ratio);
|
|
935
|
-
const fraction = ratio - index;
|
|
936
|
-
|
|
937
|
-
// Handle edge cases
|
|
938
|
-
if (index >= dataLength - 1) {
|
|
939
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
940
|
-
resampled[i] = data[dataLength - 1]!; // Last value
|
|
941
|
-
} else {
|
|
942
|
-
// Linear interpolation
|
|
943
|
-
resampled[i] = Math.round(
|
|
944
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
945
|
-
data[index]! * (1 - fraction) + data[index + 1]! * fraction,
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
return resampled;
|
|
951
|
-
}
|
|
@@ -45,15 +45,8 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
45
45
|
type: String,
|
|
46
46
|
attribute: "mode",
|
|
47
47
|
})
|
|
48
|
-
mode:
|
|
49
|
-
|
|
50
|
-
| "bars"
|
|
51
|
-
| "bricks"
|
|
52
|
-
| "line"
|
|
53
|
-
| "curve"
|
|
54
|
-
| "pixel"
|
|
55
|
-
| "wave"
|
|
56
|
-
| "spikes" = "bars";
|
|
48
|
+
mode: "roundBars" | "bars" | "bricks" | "line" | "pixel" | "wave" | "spikes" =
|
|
49
|
+
"bars";
|
|
57
50
|
|
|
58
51
|
@property({ type: String })
|
|
59
52
|
color = "currentColor";
|
|
@@ -164,7 +157,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
164
157
|
const path = new Path2D();
|
|
165
158
|
|
|
166
159
|
frequencyData.forEach((value, i) => {
|
|
167
|
-
const normalizedValue = value / 255;
|
|
160
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
168
161
|
const barHeight = normalizedValue * waveHeight;
|
|
169
162
|
const y = (waveHeight - barHeight) / 2;
|
|
170
163
|
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
@@ -190,7 +183,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
190
183
|
const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap)); // Account for gaps in height calculation
|
|
191
184
|
|
|
192
185
|
frequencyData.forEach((value, i) => {
|
|
193
|
-
const normalizedValue = value / 255;
|
|
186
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
194
187
|
const brickCount = Math.floor(normalizedValue * maxBricks);
|
|
195
188
|
|
|
196
189
|
for (let j = 0; j < brickCount; j++) {
|
|
@@ -225,7 +218,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
225
218
|
const path = new Path2D();
|
|
226
219
|
|
|
227
220
|
frequencyData.forEach((value, i) => {
|
|
228
|
-
const normalizedValue = value / 255;
|
|
221
|
+
const normalizedValue = Math.min((value / 255) * 2, 1);
|
|
229
222
|
const height = normalizedValue * waveHeight; // Use full wave height like in drawBars
|
|
230
223
|
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
231
224
|
const y = (waveHeight - height) / 2; // Center vertically
|
|
@@ -238,49 +231,52 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
238
231
|
ctx.fill(path);
|
|
239
232
|
}
|
|
240
233
|
|
|
241
|
-
protected
|
|
234
|
+
protected drawEqualizer(
|
|
235
|
+
ctx: CanvasRenderingContext2D,
|
|
236
|
+
frequencyData: Uint8Array,
|
|
237
|
+
) {
|
|
242
238
|
const canvas = ctx.canvas;
|
|
243
239
|
const waveWidth = canvas.width;
|
|
244
240
|
const waveHeight = canvas.height;
|
|
241
|
+
const baseline = waveHeight / 2;
|
|
242
|
+
const barWidth = (waveWidth / frequencyData.length) * 0.8;
|
|
245
243
|
|
|
246
244
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
247
|
-
const path = new Path2D();
|
|
248
245
|
|
|
249
|
-
//
|
|
250
|
-
const
|
|
246
|
+
// Create paths for baseline and bars
|
|
247
|
+
const baselinePath = new Path2D();
|
|
248
|
+
const barsPath = new Path2D();
|
|
251
249
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
// Draw baseline
|
|
251
|
+
baselinePath.moveTo(0, baseline);
|
|
252
|
+
baselinePath.lineTo(waveWidth, baseline);
|
|
255
253
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const lastX = waveWidth;
|
|
264
|
-
const lastY =
|
|
265
|
-
(1 - (frequencyData[frequencyData.length - 1] ?? 0) / 255) * waveHeight;
|
|
266
|
-
path.lineTo(lastX, lastY);
|
|
254
|
+
// Draw bars
|
|
255
|
+
frequencyData.forEach((value, i) => {
|
|
256
|
+
const height = (value / 255) * (waveHeight / 2);
|
|
257
|
+
const x = i * (waveWidth / frequencyData.length);
|
|
258
|
+
const y = baseline - height;
|
|
259
|
+
barsPath.rect(x, y, barWidth, Math.max(height * 2, 1));
|
|
260
|
+
});
|
|
267
261
|
|
|
268
|
-
|
|
269
|
-
ctx.
|
|
262
|
+
// Render baseline
|
|
263
|
+
ctx.lineWidth = 2;
|
|
264
|
+
ctx.stroke(baselinePath);
|
|
265
|
+
|
|
266
|
+
// Render bars
|
|
267
|
+
ctx.fill(barsPath);
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
protected
|
|
273
|
-
ctx: CanvasRenderingContext2D,
|
|
274
|
-
frequencyData: Uint8Array,
|
|
275
|
-
) {
|
|
270
|
+
protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
|
|
276
271
|
const canvas = ctx.canvas;
|
|
277
272
|
const waveWidth = canvas.width;
|
|
278
273
|
const waveHeight = canvas.height;
|
|
279
274
|
|
|
280
275
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
276
|
+
|
|
277
|
+
// Create a single Path2D object for the curve
|
|
281
278
|
const path = new Path2D();
|
|
282
279
|
|
|
283
|
-
// Draw smooth curves between points using quadratic curves
|
|
284
280
|
frequencyData.forEach((value, i) => {
|
|
285
281
|
const x = (i / frequencyData.length) * waveWidth;
|
|
286
282
|
const y = (1 - value / 255) * waveHeight;
|
|
@@ -288,11 +284,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
288
284
|
if (i === 0) {
|
|
289
285
|
path.moveTo(x, y);
|
|
290
286
|
} else {
|
|
291
|
-
|
|
292
|
-
const prevY = (1 - (frequencyData[i - 1] ?? 0) / 255) * waveHeight;
|
|
293
|
-
const xc = (prevX + x) / 2;
|
|
294
|
-
const yc = (prevY + y) / 2;
|
|
295
|
-
path.quadraticCurveTo(prevX, prevY, xc, yc);
|
|
287
|
+
path.lineTo(x, y);
|
|
296
288
|
}
|
|
297
289
|
});
|
|
298
290
|
|
|
@@ -314,7 +306,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
314
306
|
const path = new Path2D();
|
|
315
307
|
|
|
316
308
|
frequencyData.forEach((value, i) => {
|
|
317
|
-
const normalizedValue = value / 255;
|
|
309
|
+
const normalizedValue = Math.min((value / 255) * 2, 1); // Updated normalization
|
|
318
310
|
const x = i * (waveWidth / frequencyData.length);
|
|
319
311
|
const barHeight = normalizedValue * (waveHeight / 2); // Half height since we extend both ways
|
|
320
312
|
const y = baseline - barHeight;
|
|
@@ -477,8 +469,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
477
469
|
if (!ctx) return;
|
|
478
470
|
|
|
479
471
|
const frequencyData = this.targetElement.frequencyDataTask.value;
|
|
480
|
-
|
|
481
|
-
if (!frequencyData || !byteTimeData) return;
|
|
472
|
+
if (!frequencyData) return;
|
|
482
473
|
|
|
483
474
|
ctx.save();
|
|
484
475
|
if (this.color === "currentColor") {
|
|
@@ -499,10 +490,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
499
490
|
this.drawBricks(ctx, frequencyData);
|
|
500
491
|
break;
|
|
501
492
|
case "line":
|
|
502
|
-
this.drawLine(ctx,
|
|
503
|
-
break;
|
|
504
|
-
case "curve":
|
|
505
|
-
this.drawCurve(ctx, byteTimeData);
|
|
493
|
+
this.drawLine(ctx, frequencyData);
|
|
506
494
|
break;
|
|
507
495
|
case "pixel":
|
|
508
496
|
this.drawPixel(ctx, frequencyData);
|
package/src/gui/TWMixin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LitElement } from "lit";
|
|
2
2
|
// @ts-expect-error cannot figure out how to declare this module as a string
|
|
3
3
|
import twStyle from "./TWMixin.css?inline";
|
|
4
4
|
|
|
@@ -21,30 +21,13 @@ export function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {
|
|
|
21
21
|
"twSheet not found. Probable cause: CSSStyleSheet not supported in this environment",
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
const constructorStylesheets: CSSStyleSheet[] = [];
|
|
26
|
-
const constructorStyles = (("styles" in this.constructor &&
|
|
27
|
-
this.constructor.styles) ||
|
|
28
|
-
[]) as CSSResult | CSSResult[];
|
|
29
|
-
|
|
30
|
-
if (Array.isArray(constructorStyles)) {
|
|
31
|
-
for (const item of constructorStyles) {
|
|
32
|
-
if (item.styleSheet) {
|
|
33
|
-
constructorStylesheets.push(item.styleSheet);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} else if (constructorStyles.styleSheet) {
|
|
37
|
-
constructorStylesheets.push(constructorStyles.styleSheet);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
24
|
if (renderRoot?.adoptedStyleSheets) {
|
|
41
25
|
renderRoot.adoptedStyleSheets = [
|
|
42
26
|
twSheet,
|
|
43
27
|
...renderRoot.adoptedStyleSheets,
|
|
44
|
-
...constructorStylesheets,
|
|
45
28
|
];
|
|
46
29
|
} else {
|
|
47
|
-
renderRoot.adoptedStyleSheets = [twSheet
|
|
30
|
+
renderRoot.adoptedStyleSheets = [twSheet];
|
|
48
31
|
}
|
|
49
32
|
return renderRoot;
|
|
50
33
|
}
|
package/dist/getRenderInfo.d.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const RenderInfo: z.ZodObject<{
|
|
3
|
-
width: z.ZodNumber;
|
|
4
|
-
height: z.ZodNumber;
|
|
5
|
-
fps: z.ZodNumber;
|
|
6
|
-
durationMs: z.ZodNumber;
|
|
7
|
-
assets: z.ZodObject<{
|
|
8
|
-
efMedia: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
9
|
-
efCaptions: z.ZodArray<z.ZodString, "many">;
|
|
10
|
-
efImage: z.ZodArray<z.ZodString, "many">;
|
|
11
|
-
}, "strip", z.ZodTypeAny, {
|
|
12
|
-
efMedia: Record<string, any>;
|
|
13
|
-
efCaptions: string[];
|
|
14
|
-
efImage: string[];
|
|
15
|
-
}, {
|
|
16
|
-
efMedia: Record<string, any>;
|
|
17
|
-
efCaptions: string[];
|
|
18
|
-
efImage: string[];
|
|
19
|
-
}>;
|
|
20
|
-
}, "strip", z.ZodTypeAny, {
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
fps: number;
|
|
24
|
-
durationMs: number;
|
|
25
|
-
assets: {
|
|
26
|
-
efMedia: Record<string, any>;
|
|
27
|
-
efCaptions: string[];
|
|
28
|
-
efImage: string[];
|
|
29
|
-
};
|
|
30
|
-
}, {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
fps: number;
|
|
34
|
-
durationMs: number;
|
|
35
|
-
assets: {
|
|
36
|
-
efMedia: Record<string, any>;
|
|
37
|
-
efCaptions: string[];
|
|
38
|
-
efImage: string[];
|
|
39
|
-
};
|
|
40
|
-
}>;
|
|
41
|
-
export declare const getRenderInfo: () => Promise<{
|
|
42
|
-
width: number;
|
|
43
|
-
height: number;
|
|
44
|
-
fps: number;
|
|
45
|
-
durationMs: number;
|
|
46
|
-
assets: {
|
|
47
|
-
efMedia: Record<string, any>;
|
|
48
|
-
efCaptions: string[];
|
|
49
|
-
efImage: string[];
|
|
50
|
-
};
|
|
51
|
-
}>;
|
package/dist/getRenderInfo.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
const RenderInfo = z.object({
|
|
3
|
-
width: z.number().positive(),
|
|
4
|
-
height: z.number().positive(),
|
|
5
|
-
fps: z.number().positive(),
|
|
6
|
-
durationMs: z.number().positive(),
|
|
7
|
-
assets: z.object({
|
|
8
|
-
efMedia: z.record(z.any()),
|
|
9
|
-
efCaptions: z.array(z.string()),
|
|
10
|
-
efImage: z.array(z.string())
|
|
11
|
-
})
|
|
12
|
-
});
|
|
13
|
-
const getRenderInfo = async () => {
|
|
14
|
-
const rootTimeGroup = document.querySelector("ef-timegroup");
|
|
15
|
-
if (!rootTimeGroup) {
|
|
16
|
-
throw new Error("No ef-timegroup found");
|
|
17
|
-
}
|
|
18
|
-
console.error("Waiting for media durations", rootTimeGroup);
|
|
19
|
-
await rootTimeGroup.waitForMediaDurations();
|
|
20
|
-
const width = rootTimeGroup.clientWidth;
|
|
21
|
-
const height = rootTimeGroup.clientHeight;
|
|
22
|
-
const fps = 30;
|
|
23
|
-
const durationMs = Math.round(rootTimeGroup.durationMs);
|
|
24
|
-
const elements = document.querySelectorAll(
|
|
25
|
-
"ef-audio, ef-video, ef-image, ef-captions"
|
|
26
|
-
);
|
|
27
|
-
const assets = {
|
|
28
|
-
efMedia: {},
|
|
29
|
-
efCaptions: /* @__PURE__ */ new Set(),
|
|
30
|
-
efImage: /* @__PURE__ */ new Set()
|
|
31
|
-
};
|
|
32
|
-
for (const element of elements) {
|
|
33
|
-
switch (element.tagName) {
|
|
34
|
-
case "EF-AUDIO":
|
|
35
|
-
case "EF-VIDEO": {
|
|
36
|
-
const src = element.src;
|
|
37
|
-
console.error("Processing element", element.tagName, src);
|
|
38
|
-
assets.efMedia[src] = element.trackFragmentIndexLoader.value;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
case "EF-IMAGE": {
|
|
42
|
-
const src = element.src;
|
|
43
|
-
console.error("Processing element", element.tagName, src);
|
|
44
|
-
assets.efImage.add(src);
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
case "EF-CAPTIONS": {
|
|
48
|
-
const src = element.targetElement?.src;
|
|
49
|
-
console.error("Processing element", element.tagName, src);
|
|
50
|
-
assets.efCaptions.add(src);
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const renderInfo = {
|
|
56
|
-
width,
|
|
57
|
-
height,
|
|
58
|
-
fps,
|
|
59
|
-
durationMs,
|
|
60
|
-
assets: {
|
|
61
|
-
efMedia: assets.efMedia,
|
|
62
|
-
efCaptions: Array.from(assets.efCaptions),
|
|
63
|
-
efImage: Array.from(assets.efImage)
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
console.error("Render info", renderInfo);
|
|
67
|
-
return renderInfo;
|
|
68
|
-
};
|
|
69
|
-
export {
|
|
70
|
-
RenderInfo,
|
|
71
|
-
getRenderInfo
|
|
72
|
-
};
|