@meframe/core 0.0.2 → 0.0.4
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/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +6 -4
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +2 -2
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +4 -3
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +2 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +13 -8
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +7 -8
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +56 -76
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/types.d.ts +2 -3
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +1 -4
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts +1 -0
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +2 -0
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/patch.d.ts +6 -2
- package/dist/model/patch.d.ts.map +1 -1
- package/dist/model/patch.js +76 -2
- package/dist/model/patch.js.map +1 -1
- package/dist/model/types.d.ts +1 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +8 -7
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +33 -56
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +0 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +40 -19
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +3 -5
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +66 -69
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/types.d.ts +2 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts +6 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +17 -1
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
- package/dist/stages/compose/types.d.ts +2 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +0 -1
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +22 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +71 -25
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +1 -1
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +3 -2
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +2 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/utils/time-utils.d.ts +3 -2
- package/dist/utils/time-utils.d.ts.map +1 -1
- package/dist/utils/time-utils.js +2 -1
- package/dist/utils/time-utils.js.map +1 -1
- package/dist/vite-plugin.d.ts +5 -3
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +109 -52
- package/dist/vite-plugin.js.map +1 -1
- package/dist/worker/WorkerPool.d.ts +9 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +32 -5
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/{stages/demux → workers}/MP4Demuxer.js +4 -13
- package/dist/workers/MP4Demuxer.js.map +1 -0
- package/dist/workers/WorkerChannel.js +486 -0
- package/dist/workers/WorkerChannel.js.map +1 -0
- package/dist/{assets/video-demux.worker-D019I7GQ.js → workers/mp4box.all.js} +4 -912
- package/dist/workers/mp4box.all.js.map +1 -0
- package/dist/{assets/audio-compose.worker-nGVvHD5Q.js → workers/stages/compose/audio-compose.worker.js} +7 -481
- package/dist/workers/stages/compose/audio-compose.worker.js.map +1 -0
- package/dist/{assets/video-compose.worker-DPzsC21d.js → workers/stages/compose/video-compose.worker.js} +120 -562
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -0
- package/dist/{assets/decode.worker-DpWHsc7R.js → workers/stages/decode/decode.worker.js} +7 -481
- package/dist/workers/stages/decode/decode.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/demux/audio-demux.worker.js +184 -4
- package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/demux/video-demux.worker.js +2 -3
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/encode/encode.worker.js +238 -4
- package/dist/workers/stages/encode/encode.worker.js.map +1 -0
- package/dist/{stages/mux/MP4Muxer.js → workers/stages/mux/mux.worker.js} +244 -5
- package/dist/workers/stages/mux/mux.worker.js.map +1 -0
- package/package.json +21 -21
- package/dist/assets/audio-compose.worker-nGVvHD5Q.js.map +0 -1
- package/dist/assets/audio-demux.worker-xwWBtbAe.js +0 -8299
- package/dist/assets/audio-demux.worker-xwWBtbAe.js.map +0 -1
- package/dist/assets/decode.worker-DpWHsc7R.js.map +0 -1
- package/dist/assets/encode.worker-nfOb3kw6.js +0 -1026
- package/dist/assets/encode.worker-nfOb3kw6.js.map +0 -1
- package/dist/assets/mux.worker-uEMQY066.js +0 -8019
- package/dist/assets/mux.worker-uEMQY066.js.map +0 -1
- package/dist/assets/video-compose.worker-DPzsC21d.js.map +0 -1
- package/dist/assets/video-demux.worker-D019I7GQ.js.map +0 -1
- package/dist/controllers/PreviewHandle.d.ts +0 -25
- package/dist/controllers/PreviewHandle.d.ts.map +0 -1
- package/dist/controllers/PreviewHandle.js +0 -45
- package/dist/controllers/PreviewHandle.js.map +0 -1
- package/dist/model/dirty-range.js +0 -220
- package/dist/model/dirty-range.js.map +0 -1
- package/dist/model/types.js +0 -5
- package/dist/model/types.js.map +0 -1
- package/dist/plugins/BackpressureMonitor.js +0 -62
- package/dist/plugins/BackpressureMonitor.js.map +0 -1
- package/dist/stages/compose/AudioDucker.js +0 -161
- package/dist/stages/compose/AudioDucker.js.map +0 -1
- package/dist/stages/compose/AudioMixer.js +0 -373
- package/dist/stages/compose/AudioMixer.js.map +0 -1
- package/dist/stages/compose/FilterProcessor.js +0 -226
- package/dist/stages/compose/FilterProcessor.js.map +0 -1
- package/dist/stages/compose/LayerRenderer.js +0 -215
- package/dist/stages/compose/LayerRenderer.js.map +0 -1
- package/dist/stages/compose/TransitionProcessor.js +0 -189
- package/dist/stages/compose/TransitionProcessor.js.map +0 -1
- package/dist/stages/compose/VideoComposer.js +0 -186
- package/dist/stages/compose/VideoComposer.js.map +0 -1
- package/dist/stages/compose/audio-compose.worker.d.ts +0 -79
- package/dist/stages/compose/audio-compose.worker.d.ts.map +0 -1
- package/dist/stages/compose/audio-compose.worker.js +0 -540
- package/dist/stages/compose/audio-compose.worker.js.map +0 -1
- package/dist/stages/compose/audio-compose.worker2.js +0 -5
- package/dist/stages/compose/audio-compose.worker2.js.map +0 -1
- package/dist/stages/compose/video-compose.worker.d.ts +0 -60
- package/dist/stages/compose/video-compose.worker.d.ts.map +0 -1
- package/dist/stages/compose/video-compose.worker.js +0 -379
- package/dist/stages/compose/video-compose.worker.js.map +0 -1
- package/dist/stages/compose/video-compose.worker2.js +0 -5
- package/dist/stages/compose/video-compose.worker2.js.map +0 -1
- package/dist/stages/decode/AudioChunkDecoder.js +0 -82
- package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
- package/dist/stages/decode/BaseDecoder.js +0 -130
- package/dist/stages/decode/BaseDecoder.js.map +0 -1
- package/dist/stages/decode/VideoChunkDecoder.js +0 -199
- package/dist/stages/decode/VideoChunkDecoder.js.map +0 -1
- package/dist/stages/decode/decode.worker.d.ts +0 -70
- package/dist/stages/decode/decode.worker.d.ts.map +0 -1
- package/dist/stages/decode/decode.worker.js +0 -423
- package/dist/stages/decode/decode.worker.js.map +0 -1
- package/dist/stages/decode/decode.worker2.js +0 -5
- package/dist/stages/decode/decode.worker2.js.map +0 -1
- package/dist/stages/demux/MP3FrameParser.js +0 -186
- package/dist/stages/demux/MP3FrameParser.js.map +0 -1
- package/dist/stages/demux/MP4Demuxer.js.map +0 -1
- package/dist/stages/demux/audio-demux.worker.d.ts +0 -51
- package/dist/stages/demux/audio-demux.worker.d.ts.map +0 -1
- package/dist/stages/demux/audio-demux.worker.js.map +0 -1
- package/dist/stages/demux/audio-demux.worker2.js +0 -5
- package/dist/stages/demux/audio-demux.worker2.js.map +0 -1
- package/dist/stages/demux/video-demux.worker.d.ts +0 -51
- package/dist/stages/demux/video-demux.worker.d.ts.map +0 -1
- package/dist/stages/demux/video-demux.worker.js.map +0 -1
- package/dist/stages/demux/video-demux.worker2.js +0 -5
- package/dist/stages/demux/video-demux.worker2.js.map +0 -1
- package/dist/stages/encode/AudioChunkEncoder.js +0 -37
- package/dist/stages/encode/AudioChunkEncoder.js.map +0 -1
- package/dist/stages/encode/BaseEncoder.js +0 -164
- package/dist/stages/encode/BaseEncoder.js.map +0 -1
- package/dist/stages/encode/VideoChunkEncoder.js +0 -50
- package/dist/stages/encode/VideoChunkEncoder.js.map +0 -1
- package/dist/stages/encode/encode.worker.d.ts +0 -3
- package/dist/stages/encode/encode.worker.d.ts.map +0 -1
- package/dist/stages/encode/encode.worker.js.map +0 -1
- package/dist/stages/encode/encode.worker2.js +0 -5
- package/dist/stages/encode/encode.worker2.js.map +0 -1
- package/dist/stages/mux/MP4Muxer.js.map +0 -1
- package/dist/stages/mux/mux.worker.d.ts +0 -65
- package/dist/stages/mux/mux.worker.d.ts.map +0 -1
- package/dist/stages/mux/mux.worker.js +0 -219
- package/dist/stages/mux/mux.worker.js.map +0 -1
- package/dist/stages/mux/mux.worker2.js +0 -5
- package/dist/stages/mux/mux.worker2.js.map +0 -1
- package/dist/stages/mux/utils.js +0 -34
- package/dist/stages/mux/utils.js.map +0 -1
- package/dist/worker/worker-registry.d.ts +0 -12
- package/dist/worker/worker-registry.d.ts.map +0 -1
- package/dist/worker/worker-registry.js +0 -20
- package/dist/worker/worker-registry.js.map +0 -1
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
class AudioMixer {
|
|
2
|
-
config;
|
|
3
|
-
tracksMap = /* @__PURE__ */ new Map();
|
|
4
|
-
constructor(config) {
|
|
5
|
-
this.config = config;
|
|
6
|
-
}
|
|
7
|
-
getConfig() {
|
|
8
|
-
return { ...this.config };
|
|
9
|
-
}
|
|
10
|
-
updateConfig(update) {
|
|
11
|
-
this.config = { ...this.config, ...update };
|
|
12
|
-
}
|
|
13
|
-
get tracks() {
|
|
14
|
-
return Array.from(this.tracksMap.values());
|
|
15
|
-
}
|
|
16
|
-
createMixStream(ducker) {
|
|
17
|
-
return new TransformStream(
|
|
18
|
-
{
|
|
19
|
-
transform: async (request, controller) => {
|
|
20
|
-
try {
|
|
21
|
-
const frameCount = this.getFrameCount(request.durationUs);
|
|
22
|
-
if (ducker && request.duckingConfig?.enabled && frameCount > 0) {
|
|
23
|
-
const envelope = await ducker.generateDuckingEnvelope(request.tracks, frameCount);
|
|
24
|
-
for (const track of request.tracks) {
|
|
25
|
-
if (request.duckingConfig.targetTracks.includes(track.trackId)) {
|
|
26
|
-
track.duckingEnvelope = ducker.applyEnvelopeToVolume(1, envelope);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const result = await this.mixTracks(request, frameCount);
|
|
31
|
-
controller.enqueue(result);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
controller.error(error);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
highWaterMark: 2,
|
|
39
|
-
size: () => 1
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
async mixTracks(request, precomputedFrameCount) {
|
|
44
|
-
const tracks = request.tracks ?? [];
|
|
45
|
-
const frameCount = precomputedFrameCount ?? this.getFrameCount(request.durationUs);
|
|
46
|
-
const requestedChannelCount = this.config.numberOfChannels ?? 0;
|
|
47
|
-
const inferredChannelCount = tracks.reduce((max, track) => {
|
|
48
|
-
const trackChannels = track?.numberOfChannels ?? track?.audioData?.numberOfChannels ?? this.config.numberOfChannels ?? 0;
|
|
49
|
-
return trackChannels > max ? trackChannels : max;
|
|
50
|
-
}, 0);
|
|
51
|
-
const channelCount = requestedChannelCount > 0 ? requestedChannelCount : Math.max(inferredChannelCount, 1);
|
|
52
|
-
const outputChannels = Array.from({ length: channelCount }, () => {
|
|
53
|
-
return new Float32Array(frameCount);
|
|
54
|
-
});
|
|
55
|
-
for (const track of tracks) {
|
|
56
|
-
if (!track) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const resolvedAudioData = track.audioData;
|
|
60
|
-
if (!resolvedAudioData) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
this.mixTrackIntoOutput(
|
|
64
|
-
outputChannels,
|
|
65
|
-
{
|
|
66
|
-
...track,
|
|
67
|
-
audioData: resolvedAudioData,
|
|
68
|
-
numberOfChannels: track.numberOfChannels ?? resolvedAudioData.numberOfChannels ?? this.config.numberOfChannels,
|
|
69
|
-
sampleRate: track.sampleRate ?? resolvedAudioData.sampleRate ?? this.config.sampleRate
|
|
70
|
-
},
|
|
71
|
-
request.timeUs,
|
|
72
|
-
frameCount
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
const { peakLevel, rmsLevel } = this.limitAndMeasure(outputChannels);
|
|
76
|
-
const audioData = this.createAudioData(outputChannels, request.timeUs);
|
|
77
|
-
return {
|
|
78
|
-
audioData,
|
|
79
|
-
timeUs: request.timeUs,
|
|
80
|
-
durationUs: request.durationUs,
|
|
81
|
-
peakLevel,
|
|
82
|
-
rmsLevel
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
addTrack(track) {
|
|
86
|
-
this.tracksMap.set(track.id, track);
|
|
87
|
-
}
|
|
88
|
-
removeTrack(trackId) {
|
|
89
|
-
this.tracksMap.delete(trackId);
|
|
90
|
-
}
|
|
91
|
-
updateTrack(trackId, patch) {
|
|
92
|
-
const track = this.tracksMap.get(trackId);
|
|
93
|
-
if (!track) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const { config, ...rest } = patch;
|
|
97
|
-
if (config) {
|
|
98
|
-
Object.assign(track.config, config);
|
|
99
|
-
}
|
|
100
|
-
Object.assign(track, rest);
|
|
101
|
-
}
|
|
102
|
-
mixTrackIntoOutput(outputChannels, track, mixStartUs, totalFrameCount) {
|
|
103
|
-
if (totalFrameCount === 0) {
|
|
104
|
-
track.audioData.close();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (track.sampleRate !== this.config.sampleRate) {
|
|
108
|
-
track.audioData.close();
|
|
109
|
-
throw new Error("AudioMixer: sample rate mismatch");
|
|
110
|
-
}
|
|
111
|
-
const trackChannelCount = track.audioData.numberOfChannels ?? track.numberOfChannels ?? 0;
|
|
112
|
-
if (trackChannelCount === 0) {
|
|
113
|
-
track.audioData.close();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const trackChannels = this.extractChannels(track.audioData);
|
|
117
|
-
if (trackChannels.length === 0) {
|
|
118
|
-
track.audioData.close();
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const trackFrameCount = track.audioData.numberOfFrames;
|
|
122
|
-
if (trackFrameCount === 0) {
|
|
123
|
-
track.audioData.close();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const timestampUs = track.audioData.timestamp ?? mixStartUs;
|
|
127
|
-
const deltaUs = timestampUs - mixStartUs;
|
|
128
|
-
let outputOffsetFrames = Math.round(deltaUs / 1e6 * this.config.sampleRate);
|
|
129
|
-
let sourceOffsetFrames = 0;
|
|
130
|
-
if (outputOffsetFrames < 0) {
|
|
131
|
-
sourceOffsetFrames = Math.min(trackFrameCount, -outputOffsetFrames);
|
|
132
|
-
outputOffsetFrames = 0;
|
|
133
|
-
}
|
|
134
|
-
if (outputOffsetFrames >= totalFrameCount) {
|
|
135
|
-
track.audioData.close();
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const availableFrames = Math.min(
|
|
139
|
-
trackFrameCount - sourceOffsetFrames,
|
|
140
|
-
totalFrameCount - outputOffsetFrames
|
|
141
|
-
);
|
|
142
|
-
if (availableFrames <= 0) {
|
|
143
|
-
track.audioData.close();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const gains = this.buildGainEnvelope(
|
|
147
|
-
track,
|
|
148
|
-
availableFrames,
|
|
149
|
-
outputOffsetFrames,
|
|
150
|
-
sourceOffsetFrames,
|
|
151
|
-
trackFrameCount
|
|
152
|
-
);
|
|
153
|
-
const destinationChannelCount = outputChannels.length;
|
|
154
|
-
const sourceChannelCount = trackChannels.length;
|
|
155
|
-
for (let channelIndex = 0; channelIndex < destinationChannelCount; channelIndex++) {
|
|
156
|
-
const destination = outputChannels[channelIndex];
|
|
157
|
-
const source = trackChannels[channelIndex] ?? trackChannels[sourceChannelCount - 1];
|
|
158
|
-
if (!destination || !source) continue;
|
|
159
|
-
for (let frameIndex = 0; frameIndex < availableFrames; frameIndex++) {
|
|
160
|
-
const sample = source[sourceOffsetFrames + frameIndex] ?? 0;
|
|
161
|
-
const gain = gains[frameIndex] ?? 0;
|
|
162
|
-
destination[outputOffsetFrames + frameIndex] = (destination[outputOffsetFrames + frameIndex] ?? 0) + sample * gain;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
track.audioData.close();
|
|
166
|
-
}
|
|
167
|
-
buildGainEnvelope(track, length, outputOffsetFrames, sourceOffsetFrames, trackFrameCount) {
|
|
168
|
-
const gains = new Float32Array(length);
|
|
169
|
-
const baseVolume = typeof track.config.volume === "number" ? track.config.volume : 1;
|
|
170
|
-
gains.fill(baseVolume);
|
|
171
|
-
const fadeInSamples = this.getFadeSampleCount(track.config.fadeIn);
|
|
172
|
-
const fadeOutSamples = this.getFadeSampleCount(track.config.fadeOut);
|
|
173
|
-
const clipDurationSamples = this.getClipSampleCount(track.config.durationUs) || trackFrameCount;
|
|
174
|
-
const trackStartFrame = this.computeTrackStartFrame(track);
|
|
175
|
-
for (let i = 0; i < length; i++) {
|
|
176
|
-
const envelopeIndex = outputOffsetFrames + i;
|
|
177
|
-
const absoluteFrame = trackStartFrame + sourceOffsetFrames + i;
|
|
178
|
-
let gain = baseVolume;
|
|
179
|
-
if (fadeInSamples > 0 && absoluteFrame < fadeInSamples) {
|
|
180
|
-
const progress = Math.min(1, absoluteFrame / fadeInSamples);
|
|
181
|
-
gain *= this.getCurveValue(progress, track.config.fadeIn?.curve);
|
|
182
|
-
}
|
|
183
|
-
if (fadeOutSamples > 0 && clipDurationSamples > 0) {
|
|
184
|
-
const fadeStart = Math.max(0, clipDurationSamples - fadeOutSamples);
|
|
185
|
-
if (absoluteFrame >= fadeStart) {
|
|
186
|
-
const progress = Math.min(1, (absoluteFrame - fadeStart) / fadeOutSamples);
|
|
187
|
-
const remaining = Math.max(0, 1 - progress);
|
|
188
|
-
gain *= this.getCurveValue(remaining, track.config.fadeOut?.curve);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (track.duckingEnvelope && envelopeIndex < track.duckingEnvelope.length && envelopeIndex >= 0) {
|
|
192
|
-
gain *= track.duckingEnvelope[envelopeIndex] ?? 1;
|
|
193
|
-
}
|
|
194
|
-
gains[i] = gain;
|
|
195
|
-
}
|
|
196
|
-
return gains;
|
|
197
|
-
}
|
|
198
|
-
extractChannels(audioData) {
|
|
199
|
-
const configuredChannels = this.config.numberOfChannels ?? 0;
|
|
200
|
-
const channelCount = audioData.numberOfChannels ?? configuredChannels;
|
|
201
|
-
const frameCount = audioData.numberOfFrames;
|
|
202
|
-
const format = audioData.format ?? "f32";
|
|
203
|
-
if (!channelCount || !frameCount) {
|
|
204
|
-
return [];
|
|
205
|
-
}
|
|
206
|
-
const toFloat = (value) => value / 32768;
|
|
207
|
-
const zeroChannels = () => Array.from(
|
|
208
|
-
{ length: configuredChannels || channelCount },
|
|
209
|
-
() => new Float32Array(frameCount)
|
|
210
|
-
);
|
|
211
|
-
if (format === "f32") {
|
|
212
|
-
const interleaved = new Float32Array(frameCount * channelCount);
|
|
213
|
-
audioData.copyTo(interleaved, { format: "f32", planeIndex: 0 });
|
|
214
|
-
const channels2 = zeroChannels();
|
|
215
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
216
|
-
const offset = frame * channelCount;
|
|
217
|
-
for (let channel = 0; channel < channels2.length; channel++) {
|
|
218
|
-
const channelArray = channels2[channel];
|
|
219
|
-
if (!channelArray) continue;
|
|
220
|
-
const sourceChannel = channel < channelCount ? channel : channelCount - 1;
|
|
221
|
-
channelArray[frame] = interleaved[offset + sourceChannel] ?? 0;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return channels2;
|
|
225
|
-
}
|
|
226
|
-
if (format === "s16") {
|
|
227
|
-
const interleaved = new Int16Array(frameCount * channelCount);
|
|
228
|
-
audioData.copyTo(interleaved, { format: "s16", planeIndex: 0 });
|
|
229
|
-
const channels2 = zeroChannels();
|
|
230
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
231
|
-
const offset = frame * channelCount;
|
|
232
|
-
for (let channel = 0; channel < channels2.length; channel++) {
|
|
233
|
-
const channelArray = channels2[channel];
|
|
234
|
-
if (!channelArray) continue;
|
|
235
|
-
const sourceChannel = channel < channelCount ? channel : channelCount - 1;
|
|
236
|
-
channelArray[frame] = toFloat(interleaved[offset + sourceChannel] ?? 0);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return channels2;
|
|
240
|
-
}
|
|
241
|
-
if (format === "f32-planar") {
|
|
242
|
-
const channels2 = zeroChannels();
|
|
243
|
-
for (let channel = 0; channel < channels2.length; channel++) {
|
|
244
|
-
const channelArray = channels2[channel];
|
|
245
|
-
if (!channelArray) continue;
|
|
246
|
-
const sourceChannel = channel < channelCount ? channel : channelCount - 1;
|
|
247
|
-
audioData.copyTo(channelArray, { planeIndex: sourceChannel, format: "f32-planar" });
|
|
248
|
-
}
|
|
249
|
-
return channels2;
|
|
250
|
-
}
|
|
251
|
-
if (format === "s16-planar") {
|
|
252
|
-
const tmp = new Int16Array(frameCount);
|
|
253
|
-
const channels2 = zeroChannels();
|
|
254
|
-
for (let channel = 0; channel < channels2.length; channel++) {
|
|
255
|
-
const channelArray = channels2[channel];
|
|
256
|
-
if (!channelArray) continue;
|
|
257
|
-
const sourceChannel = channel < channelCount ? channel : channelCount - 1;
|
|
258
|
-
audioData.copyTo(tmp, { planeIndex: sourceChannel, format: "s16-planar" });
|
|
259
|
-
for (let i = 0; i < frameCount; i++) {
|
|
260
|
-
channelArray[i] = toFloat(tmp[i] ?? 0);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return channels2;
|
|
264
|
-
}
|
|
265
|
-
const channels = zeroChannels();
|
|
266
|
-
for (let channel = 0; channel < channels.length; channel++) {
|
|
267
|
-
const channelArray = channels[channel];
|
|
268
|
-
if (!channelArray) continue;
|
|
269
|
-
const sourceChannel = channel < channelCount ? channel : channelCount - 1;
|
|
270
|
-
audioData.copyTo(channelArray, { planeIndex: sourceChannel });
|
|
271
|
-
}
|
|
272
|
-
return channels;
|
|
273
|
-
}
|
|
274
|
-
limitAndMeasure(channels) {
|
|
275
|
-
let peak = 0;
|
|
276
|
-
let sumSquares = 0;
|
|
277
|
-
let samples = 0;
|
|
278
|
-
for (const channel of channels) {
|
|
279
|
-
for (let i = 0; i < channel.length; i++) {
|
|
280
|
-
let sample = channel[i] ?? 0;
|
|
281
|
-
if (sample > 1) {
|
|
282
|
-
sample = 1;
|
|
283
|
-
} else if (sample < -1) {
|
|
284
|
-
sample = -1;
|
|
285
|
-
}
|
|
286
|
-
channel[i] = sample;
|
|
287
|
-
const absSample = Math.abs(sample);
|
|
288
|
-
if (absSample > peak) {
|
|
289
|
-
peak = absSample;
|
|
290
|
-
}
|
|
291
|
-
sumSquares += sample * sample;
|
|
292
|
-
samples++;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
const rmsLevel = samples > 0 ? Math.sqrt(sumSquares / samples) : 0;
|
|
296
|
-
return {
|
|
297
|
-
peakLevel: peak,
|
|
298
|
-
rmsLevel
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
createAudioData(channels, timestampUs) {
|
|
302
|
-
const configuredChannels = this.config.numberOfChannels ?? 0;
|
|
303
|
-
const inferredChannels = channels.length;
|
|
304
|
-
const numberOfChannels = (inferredChannels > 0 ? inferredChannels : configuredChannels) || 1;
|
|
305
|
-
const numberOfFrames = channels[0]?.length ?? 0;
|
|
306
|
-
if (numberOfFrames === 0) {
|
|
307
|
-
return new AudioData({
|
|
308
|
-
format: "f32",
|
|
309
|
-
sampleRate: this.config.sampleRate,
|
|
310
|
-
numberOfFrames: 0,
|
|
311
|
-
numberOfChannels,
|
|
312
|
-
timestamp: timestampUs,
|
|
313
|
-
data: new Float32Array(0)
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
const interleaved = new Float32Array(numberOfFrames * numberOfChannels);
|
|
317
|
-
for (let frame = 0; frame < numberOfFrames; frame++) {
|
|
318
|
-
for (let channel = 0; channel < numberOfChannels; channel++) {
|
|
319
|
-
const sourceChannel = channels[channel] ?? channels[channels.length - 1];
|
|
320
|
-
interleaved[frame * numberOfChannels + channel] = sourceChannel?.[frame] ?? 0;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return new AudioData({
|
|
324
|
-
format: "f32",
|
|
325
|
-
sampleRate: this.config.sampleRate,
|
|
326
|
-
numberOfFrames,
|
|
327
|
-
numberOfChannels,
|
|
328
|
-
timestamp: timestampUs,
|
|
329
|
-
data: interleaved
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
getFrameCount(durationUs) {
|
|
333
|
-
if (durationUs <= 0) {
|
|
334
|
-
return 0;
|
|
335
|
-
}
|
|
336
|
-
return Math.ceil(durationUs / 1e6 * this.config.sampleRate);
|
|
337
|
-
}
|
|
338
|
-
getFadeSampleCount(fade) {
|
|
339
|
-
if (!fade || fade.durationUs <= 0) {
|
|
340
|
-
return 0;
|
|
341
|
-
}
|
|
342
|
-
return Math.round(fade.durationUs / 1e6 * this.config.sampleRate);
|
|
343
|
-
}
|
|
344
|
-
getClipSampleCount(durationUs) {
|
|
345
|
-
if (!durationUs || durationUs <= 0) {
|
|
346
|
-
return 0;
|
|
347
|
-
}
|
|
348
|
-
return Math.round(durationUs / 1e6 * this.config.sampleRate);
|
|
349
|
-
}
|
|
350
|
-
computeTrackStartFrame(track) {
|
|
351
|
-
const audioTimestamp = track.audioData.timestamp ?? track.config.startTimeUs;
|
|
352
|
-
const relativeUs = audioTimestamp - track.config.startTimeUs;
|
|
353
|
-
const relativeFrames = Math.round(relativeUs / 1e6 * this.config.sampleRate);
|
|
354
|
-
return relativeFrames > 0 ? relativeFrames : 0;
|
|
355
|
-
}
|
|
356
|
-
getCurveValue(progress, curve = "linear") {
|
|
357
|
-
const clamped = Math.min(Math.max(progress, 0), 1);
|
|
358
|
-
switch (curve) {
|
|
359
|
-
case "exponential":
|
|
360
|
-
return clamped * clamped;
|
|
361
|
-
case "logarithmic":
|
|
362
|
-
return Math.log10(clamped * 9 + 1);
|
|
363
|
-
case "cosine":
|
|
364
|
-
return (1 - Math.cos(clamped * Math.PI)) / 2;
|
|
365
|
-
default:
|
|
366
|
-
return clamped;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
export {
|
|
371
|
-
AudioMixer
|
|
372
|
-
};
|
|
373
|
-
//# sourceMappingURL=AudioMixer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AudioMixer.js","sources":["../../../src/stages/compose/AudioMixer.ts"],"sourcesContent":["import type {\n AudioComposeConfig,\n AudioTrack,\n AudioTrackConfig,\n FadeConfig,\n MixRequest,\n MixResult,\n} from './types';\nimport type { AudioDucker } from './AudioDucker';\n\nexport class AudioMixer {\n config: AudioComposeConfig;\n private tracksMap = new Map<string, AudioTrack>();\n\n constructor(config: AudioComposeConfig) {\n this.config = config;\n }\n\n getConfig(): AudioComposeConfig {\n return { ...this.config };\n }\n\n updateConfig(update: Partial<AudioComposeConfig>): void {\n this.config = { ...this.config, ...update };\n }\n\n get tracks(): AudioTrack[] {\n return Array.from(this.tracksMap.values());\n }\n\n createMixStream(ducker?: AudioDucker): TransformStream<MixRequest, MixResult> {\n return new TransformStream<MixRequest, MixResult>(\n {\n transform: async (request, controller) => {\n try {\n const frameCount = this.getFrameCount(request.durationUs);\n\n if (ducker && request.duckingConfig?.enabled && frameCount > 0) {\n const envelope = await ducker.generateDuckingEnvelope(request.tracks, frameCount);\n\n for (const track of request.tracks) {\n if (request.duckingConfig.targetTracks.includes(track.trackId)) {\n track.duckingEnvelope = ducker.applyEnvelopeToVolume(1, envelope);\n }\n }\n }\n\n const result = await this.mixTracks(request, frameCount);\n controller.enqueue(result);\n } catch (error) {\n controller.error(error);\n }\n },\n },\n {\n highWaterMark: 2,\n size: () => 1,\n }\n );\n }\n\n async mixTracks(request: MixRequest, precomputedFrameCount?: number): Promise<MixResult> {\n const tracks = request.tracks ?? [];\n const frameCount = precomputedFrameCount ?? this.getFrameCount(request.durationUs);\n\n const requestedChannelCount = this.config.numberOfChannels ?? 0;\n const inferredChannelCount = tracks.reduce((max, track) => {\n const trackChannels =\n track?.numberOfChannels ??\n track?.audioData?.numberOfChannels ??\n this.config.numberOfChannels ??\n 0;\n return trackChannels > max ? trackChannels : max;\n }, 0);\n const channelCount =\n requestedChannelCount > 0 ? requestedChannelCount : Math.max(inferredChannelCount, 1);\n\n const outputChannels: Float32Array[] = Array.from({ length: channelCount }, () => {\n return new Float32Array(frameCount);\n });\n\n for (const track of tracks) {\n if (!track) {\n continue;\n }\n\n const resolvedAudioData = track.audioData;\n if (!resolvedAudioData) {\n continue;\n }\n\n this.mixTrackIntoOutput(\n outputChannels,\n {\n ...track,\n audioData: resolvedAudioData,\n numberOfChannels:\n track.numberOfChannels ??\n resolvedAudioData.numberOfChannels ??\n this.config.numberOfChannels,\n sampleRate: track.sampleRate ?? resolvedAudioData.sampleRate ?? this.config.sampleRate,\n },\n request.timeUs,\n frameCount\n );\n }\n\n const { peakLevel, rmsLevel } = this.limitAndMeasure(outputChannels);\n const audioData = this.createAudioData(outputChannels, request.timeUs);\n\n return {\n audioData,\n timeUs: request.timeUs,\n durationUs: request.durationUs,\n peakLevel,\n rmsLevel,\n };\n }\n\n addTrack(track: AudioTrack): void {\n this.tracksMap.set(track.id, track);\n }\n\n removeTrack(trackId: string): void {\n this.tracksMap.delete(trackId);\n }\n\n updateTrack(\n trackId: string,\n patch: Partial<AudioTrack> & { config?: Partial<AudioTrackConfig> }\n ): void {\n const track = this.tracksMap.get(trackId);\n if (!track) {\n return;\n }\n\n const { config, ...rest } = patch;\n if (config) {\n Object.assign(track.config, config);\n }\n Object.assign(track, rest);\n }\n\n private mixTrackIntoOutput(\n outputChannels: Float32Array[],\n track: MixRequest['tracks'][0],\n mixStartUs: number,\n totalFrameCount: number\n ): void {\n if (totalFrameCount === 0) {\n track.audioData.close();\n return;\n }\n\n if (track.sampleRate !== this.config.sampleRate) {\n track.audioData.close();\n throw new Error('AudioMixer: sample rate mismatch');\n }\n\n const trackChannelCount = track.audioData.numberOfChannels ?? track.numberOfChannels ?? 0;\n if (trackChannelCount === 0) {\n track.audioData.close();\n return;\n }\n\n const trackChannels = this.extractChannels(track.audioData);\n if (trackChannels.length === 0) {\n track.audioData.close();\n return;\n }\n const trackFrameCount = track.audioData.numberOfFrames;\n\n if (trackFrameCount === 0) {\n track.audioData.close();\n return;\n }\n\n const timestampUs = track.audioData.timestamp ?? mixStartUs;\n const deltaUs = timestampUs - mixStartUs;\n let outputOffsetFrames = Math.round((deltaUs / 1_000_000) * this.config.sampleRate);\n let sourceOffsetFrames = 0;\n\n if (outputOffsetFrames < 0) {\n sourceOffsetFrames = Math.min(trackFrameCount, -outputOffsetFrames);\n outputOffsetFrames = 0;\n }\n\n if (outputOffsetFrames >= totalFrameCount) {\n track.audioData.close();\n return;\n }\n\n const availableFrames = Math.min(\n trackFrameCount - sourceOffsetFrames,\n totalFrameCount - outputOffsetFrames\n );\n\n if (availableFrames <= 0) {\n track.audioData.close();\n return;\n }\n\n const gains = this.buildGainEnvelope(\n track,\n availableFrames,\n outputOffsetFrames,\n sourceOffsetFrames,\n trackFrameCount\n );\n\n const destinationChannelCount = outputChannels.length;\n const sourceChannelCount = trackChannels.length;\n\n for (let channelIndex = 0; channelIndex < destinationChannelCount; channelIndex++) {\n const destination = outputChannels[channelIndex];\n const source = trackChannels[channelIndex] ?? trackChannels[sourceChannelCount - 1];\n if (!destination || !source) continue;\n\n for (let frameIndex = 0; frameIndex < availableFrames; frameIndex++) {\n const sample = source[sourceOffsetFrames + frameIndex] ?? 0;\n const gain = gains[frameIndex] ?? 0;\n destination[outputOffsetFrames + frameIndex] =\n (destination[outputOffsetFrames + frameIndex] ?? 0) + sample * gain;\n }\n }\n\n track.audioData.close();\n }\n\n private buildGainEnvelope(\n track: MixRequest['tracks'][0],\n length: number,\n outputOffsetFrames: number,\n sourceOffsetFrames: number,\n trackFrameCount: number\n ): Float32Array {\n const gains = new Float32Array(length);\n const baseVolume = typeof track.config.volume === 'number' ? track.config.volume : 1;\n gains.fill(baseVolume);\n\n const fadeInSamples = this.getFadeSampleCount(track.config.fadeIn);\n const fadeOutSamples = this.getFadeSampleCount(track.config.fadeOut);\n const clipDurationSamples = this.getClipSampleCount(track.config.durationUs) || trackFrameCount;\n const trackStartFrame = this.computeTrackStartFrame(track);\n\n for (let i = 0; i < length; i++) {\n const envelopeIndex = outputOffsetFrames + i;\n const absoluteFrame = trackStartFrame + sourceOffsetFrames + i;\n let gain = baseVolume;\n\n if (fadeInSamples > 0 && absoluteFrame < fadeInSamples) {\n const progress = Math.min(1, absoluteFrame / fadeInSamples);\n gain *= this.getCurveValue(progress, track.config.fadeIn?.curve);\n }\n\n if (fadeOutSamples > 0 && clipDurationSamples > 0) {\n const fadeStart = Math.max(0, clipDurationSamples - fadeOutSamples);\n if (absoluteFrame >= fadeStart) {\n const progress = Math.min(1, (absoluteFrame - fadeStart) / fadeOutSamples);\n const remaining = Math.max(0, 1 - progress);\n gain *= this.getCurveValue(remaining, track.config.fadeOut?.curve);\n }\n }\n\n if (\n track.duckingEnvelope &&\n envelopeIndex < track.duckingEnvelope.length &&\n envelopeIndex >= 0\n ) {\n gain *= track.duckingEnvelope[envelopeIndex] ?? 1;\n }\n\n gains[i] = gain;\n }\n\n return gains;\n }\n\n private extractChannels(audioData: AudioData): Float32Array[] {\n const configuredChannels = this.config.numberOfChannels ?? 0;\n const channelCount = audioData.numberOfChannels ?? configuredChannels;\n const frameCount = audioData.numberOfFrames;\n const format: string = (audioData as any).format ?? 'f32';\n\n if (!channelCount || !frameCount) {\n return [];\n }\n\n const toFloat = (value: number): number => value / 32768;\n\n const zeroChannels = (): Float32Array[] =>\n Array.from(\n { length: configuredChannels || channelCount },\n () => new Float32Array(frameCount)\n );\n\n if (format === 'f32') {\n const interleaved = new Float32Array(frameCount * channelCount);\n audioData.copyTo(interleaved, { format: 'f32', planeIndex: 0 });\n const channels = zeroChannels();\n for (let frame = 0; frame < frameCount; frame++) {\n const offset = frame * channelCount;\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n channelArray[frame] = interleaved[offset + sourceChannel] ?? 0;\n }\n }\n return channels;\n }\n\n if (format === 's16') {\n const interleaved = new Int16Array(frameCount * channelCount);\n audioData.copyTo(interleaved, { format: 's16', planeIndex: 0 });\n const channels = zeroChannels();\n for (let frame = 0; frame < frameCount; frame++) {\n const offset = frame * channelCount;\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n channelArray[frame] = toFloat(interleaved[offset + sourceChannel] ?? 0);\n }\n }\n return channels;\n }\n\n if (format === 'f32-planar') {\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(channelArray, { planeIndex: sourceChannel, format: 'f32-planar' });\n }\n return channels;\n }\n\n if (format === 's16-planar') {\n const tmp = new Int16Array(frameCount);\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(tmp, { planeIndex: sourceChannel, format: 's16-planar' as any });\n for (let i = 0; i < frameCount; i++) {\n channelArray[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return channels;\n }\n\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(channelArray, { planeIndex: sourceChannel });\n }\n return channels;\n }\n\n private limitAndMeasure(channels: Float32Array[]): { peakLevel: number; rmsLevel: number } {\n let peak = 0;\n let sumSquares = 0;\n let samples = 0;\n\n for (const channel of channels) {\n for (let i = 0; i < channel.length; i++) {\n let sample = channel[i] ?? 0;\n if (sample > 1) {\n sample = 1;\n } else if (sample < -1) {\n sample = -1;\n }\n\n channel[i] = sample;\n\n const absSample = Math.abs(sample);\n if (absSample > peak) {\n peak = absSample;\n }\n\n sumSquares += sample * sample;\n samples++;\n }\n }\n\n const rmsLevel = samples > 0 ? Math.sqrt(sumSquares / samples) : 0;\n\n return {\n peakLevel: peak,\n rmsLevel,\n };\n }\n\n private createAudioData(channels: Float32Array[], timestampUs: number): AudioData {\n const configuredChannels = this.config.numberOfChannels ?? 0;\n const inferredChannels = channels.length;\n const numberOfChannels = (inferredChannels > 0 ? inferredChannels : configuredChannels) || 1;\n const numberOfFrames = channels[0]?.length ?? 0;\n // console.log('>>>>>>>>>>>>>> createAudioData', channels, this.config, numberOfChannels, numberOfFrames);\n if (numberOfFrames === 0) {\n return new AudioData({\n format: 'f32',\n sampleRate: this.config.sampleRate,\n numberOfFrames: 0,\n numberOfChannels,\n timestamp: timestampUs,\n data: new Float32Array(0),\n });\n }\n\n const interleaved = new Float32Array(numberOfFrames * numberOfChannels);\n\n for (let frame = 0; frame < numberOfFrames; frame++) {\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const sourceChannel = channels[channel] ?? channels[channels.length - 1];\n interleaved[frame * numberOfChannels + channel] = sourceChannel?.[frame] ?? 0;\n }\n }\n\n return new AudioData({\n format: 'f32',\n sampleRate: this.config.sampleRate,\n numberOfFrames,\n numberOfChannels,\n timestamp: timestampUs,\n data: interleaved,\n });\n }\n\n private getFrameCount(durationUs: number): number {\n if (durationUs <= 0) {\n return 0;\n }\n\n return Math.ceil((durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private getFadeSampleCount(fade?: FadeConfig): number {\n if (!fade || fade.durationUs <= 0) {\n return 0;\n }\n\n return Math.round((fade.durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private getClipSampleCount(durationUs?: number): number {\n if (!durationUs || durationUs <= 0) {\n return 0;\n }\n\n return Math.round((durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private computeTrackStartFrame(track: MixRequest['tracks'][0]): number {\n const audioTimestamp = track.audioData.timestamp ?? track.config.startTimeUs;\n const relativeUs = audioTimestamp - track.config.startTimeUs;\n const relativeFrames = Math.round((relativeUs / 1_000_000) * this.config.sampleRate);\n return relativeFrames > 0 ? relativeFrames : 0;\n }\n\n private getCurveValue(progress: number, curve: FadeConfig['curve'] = 'linear'): number {\n const clamped = Math.min(Math.max(progress, 0), 1);\n\n switch (curve) {\n case 'exponential':\n return clamped * clamped;\n case 'logarithmic':\n return Math.log10(clamped * 9 + 1);\n case 'cosine':\n return (1 - Math.cos(clamped * Math.PI)) / 2;\n default:\n return clamped;\n }\n }\n}\n"],"names":["channels"],"mappings":"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"}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
class FilterProcessor {
|
|
2
|
-
filterCache = /* @__PURE__ */ new Map();
|
|
3
|
-
/**
|
|
4
|
-
* Apply filters to canvas context
|
|
5
|
-
* Combines multiple filters into a single CSS filter string for performance
|
|
6
|
-
*/
|
|
7
|
-
applyFilters(ctx, filters) {
|
|
8
|
-
if (!filters || filters.length === 0) {
|
|
9
|
-
ctx.filter = "none";
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const cacheKey = this.generateCacheKey(filters);
|
|
13
|
-
let filterString = this.filterCache.get(cacheKey);
|
|
14
|
-
if (!filterString) {
|
|
15
|
-
filterString = this.buildFilterString(filters);
|
|
16
|
-
this.filterCache.set(cacheKey, filterString);
|
|
17
|
-
}
|
|
18
|
-
ctx.filter = filterString;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Build CSS filter string from filter array
|
|
22
|
-
*/
|
|
23
|
-
buildFilterString(filters) {
|
|
24
|
-
const filterStrings = [];
|
|
25
|
-
for (const filter of filters) {
|
|
26
|
-
const filterStr = this.buildSingleFilter(filter);
|
|
27
|
-
if (filterStr) {
|
|
28
|
-
filterStrings.push(filterStr);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return filterStrings.length > 0 ? filterStrings.join(" ") : "none";
|
|
32
|
-
}
|
|
33
|
-
buildSingleFilter(filter) {
|
|
34
|
-
switch (filter.type) {
|
|
35
|
-
case "blur":
|
|
36
|
-
return `blur(${filter.value ?? 0}px)`;
|
|
37
|
-
case "brightness":
|
|
38
|
-
return `brightness(${filter.value ?? 1})`;
|
|
39
|
-
case "contrast":
|
|
40
|
-
return `contrast(${filter.value ?? 1})`;
|
|
41
|
-
case "grayscale":
|
|
42
|
-
return `grayscale(${filter.value ?? 0})`;
|
|
43
|
-
case "hue-rotate":
|
|
44
|
-
return `hue-rotate(${filter.value ?? 0}deg)`;
|
|
45
|
-
case "saturate":
|
|
46
|
-
return `saturate(${filter.value ?? 1})`;
|
|
47
|
-
case "sepia":
|
|
48
|
-
return `sepia(${filter.value ?? 0})`;
|
|
49
|
-
case "custom":
|
|
50
|
-
return this.buildCustomFilter(filter);
|
|
51
|
-
default:
|
|
52
|
-
console.warn(`Unknown filter type: ${filter.type}`);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Build custom filter from params
|
|
58
|
-
*/
|
|
59
|
-
buildCustomFilter(filter) {
|
|
60
|
-
if (!filter.params) return null;
|
|
61
|
-
const { type, ...params } = filter.params;
|
|
62
|
-
switch (type) {
|
|
63
|
-
case "drop-shadow":
|
|
64
|
-
return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;
|
|
65
|
-
case "opacity":
|
|
66
|
-
return `opacity(${params.value})`;
|
|
67
|
-
case "invert":
|
|
68
|
-
return `invert(${params.value})`;
|
|
69
|
-
default:
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Apply color matrix transformation for advanced effects
|
|
75
|
-
* This allows for more complex color manipulations than CSS filters
|
|
76
|
-
*/
|
|
77
|
-
applyColorMatrix(imageData, matrix) {
|
|
78
|
-
if (matrix.length !== 20) {
|
|
79
|
-
throw new Error("Color matrix must have 20 values (4x5 matrix)");
|
|
80
|
-
}
|
|
81
|
-
const data = imageData.data;
|
|
82
|
-
const length = data.length;
|
|
83
|
-
for (let i = 0; i < length; i += 4) {
|
|
84
|
-
const r = data[i];
|
|
85
|
-
const g = data[i + 1];
|
|
86
|
-
const b = data[i + 2];
|
|
87
|
-
const a = data[i + 3];
|
|
88
|
-
const m = matrix;
|
|
89
|
-
data[i] = this.clamp(r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255);
|
|
90
|
-
data[i + 1] = this.clamp(r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255);
|
|
91
|
-
data[i + 2] = this.clamp(r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255);
|
|
92
|
-
data[i + 3] = this.clamp(r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255);
|
|
93
|
-
}
|
|
94
|
-
return imageData;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Predefined color matrices for common effects
|
|
98
|
-
*/
|
|
99
|
-
getPresetMatrix(preset) {
|
|
100
|
-
switch (preset) {
|
|
101
|
-
case "vintage":
|
|
102
|
-
return [
|
|
103
|
-
0.393,
|
|
104
|
-
0.769,
|
|
105
|
-
0.189,
|
|
106
|
-
0,
|
|
107
|
-
0,
|
|
108
|
-
0.349,
|
|
109
|
-
0.686,
|
|
110
|
-
0.168,
|
|
111
|
-
0,
|
|
112
|
-
0,
|
|
113
|
-
0.272,
|
|
114
|
-
0.534,
|
|
115
|
-
0.131,
|
|
116
|
-
0,
|
|
117
|
-
0,
|
|
118
|
-
0,
|
|
119
|
-
0,
|
|
120
|
-
0,
|
|
121
|
-
1,
|
|
122
|
-
0
|
|
123
|
-
];
|
|
124
|
-
case "noir":
|
|
125
|
-
return [
|
|
126
|
-
0.25,
|
|
127
|
-
0.25,
|
|
128
|
-
0.25,
|
|
129
|
-
0,
|
|
130
|
-
0,
|
|
131
|
-
0.25,
|
|
132
|
-
0.25,
|
|
133
|
-
0.25,
|
|
134
|
-
0,
|
|
135
|
-
0,
|
|
136
|
-
0.25,
|
|
137
|
-
0.25,
|
|
138
|
-
0.25,
|
|
139
|
-
0,
|
|
140
|
-
0,
|
|
141
|
-
0,
|
|
142
|
-
0,
|
|
143
|
-
0,
|
|
144
|
-
1,
|
|
145
|
-
0
|
|
146
|
-
];
|
|
147
|
-
case "cool":
|
|
148
|
-
return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];
|
|
149
|
-
case "warm":
|
|
150
|
-
return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];
|
|
151
|
-
default:
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Apply Gaussian blur manually (for cases where CSS filter is not enough)
|
|
157
|
-
*/
|
|
158
|
-
applyGaussianBlur(imageData, radius) {
|
|
159
|
-
const output = new ImageData(
|
|
160
|
-
new Uint8ClampedArray(imageData.data),
|
|
161
|
-
imageData.width,
|
|
162
|
-
imageData.height
|
|
163
|
-
);
|
|
164
|
-
const width = imageData.width;
|
|
165
|
-
const height = imageData.height;
|
|
166
|
-
const data = imageData.data;
|
|
167
|
-
const outData = output.data;
|
|
168
|
-
for (let y = 0; y < height; y++) {
|
|
169
|
-
for (let x = 0; x < width; x++) {
|
|
170
|
-
let r = 0, g = 0, b = 0, a = 0;
|
|
171
|
-
let count = 0;
|
|
172
|
-
for (let dx = -radius; dx <= radius; dx++) {
|
|
173
|
-
const nx = Math.min(Math.max(x + dx, 0), width - 1);
|
|
174
|
-
const idx2 = (y * width + nx) * 4;
|
|
175
|
-
r += data[idx2];
|
|
176
|
-
g += data[idx2 + 1];
|
|
177
|
-
b += data[idx2 + 2];
|
|
178
|
-
a += data[idx2 + 3];
|
|
179
|
-
count++;
|
|
180
|
-
}
|
|
181
|
-
const idx = (y * width + x) * 4;
|
|
182
|
-
outData[idx] = r / count;
|
|
183
|
-
outData[idx + 1] = g / count;
|
|
184
|
-
outData[idx + 2] = b / count;
|
|
185
|
-
outData[idx + 3] = a / count;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
for (let x = 0; x < width; x++) {
|
|
189
|
-
for (let y = 0; y < height; y++) {
|
|
190
|
-
let r = 0, g = 0, b = 0, a = 0;
|
|
191
|
-
let count = 0;
|
|
192
|
-
for (let dy = -radius; dy <= radius; dy++) {
|
|
193
|
-
const ny = Math.min(Math.max(y + dy, 0), height - 1);
|
|
194
|
-
const idx2 = (ny * width + x) * 4;
|
|
195
|
-
r += outData[idx2];
|
|
196
|
-
g += outData[idx2 + 1];
|
|
197
|
-
b += outData[idx2 + 2];
|
|
198
|
-
a += outData[idx2 + 3];
|
|
199
|
-
count++;
|
|
200
|
-
}
|
|
201
|
-
const idx = (y * width + x) * 4;
|
|
202
|
-
data[idx] = r / count;
|
|
203
|
-
data[idx + 1] = g / count;
|
|
204
|
-
data[idx + 2] = b / count;
|
|
205
|
-
data[idx + 3] = a / count;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return imageData;
|
|
209
|
-
}
|
|
210
|
-
clamp(value) {
|
|
211
|
-
return Math.min(255, Math.max(0, Math.round(value)));
|
|
212
|
-
}
|
|
213
|
-
generateCacheKey(filters) {
|
|
214
|
-
return filters.map((f) => `${f.type}:${f.value ?? "default"}`).join("|");
|
|
215
|
-
}
|
|
216
|
-
clearCache() {
|
|
217
|
-
this.filterCache.clear();
|
|
218
|
-
}
|
|
219
|
-
getCacheSize() {
|
|
220
|
-
return this.filterCache.size;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
export {
|
|
224
|
-
FilterProcessor
|
|
225
|
-
};
|
|
226
|
-
//# sourceMappingURL=FilterProcessor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FilterProcessor.js","sources":["../../../src/stages/compose/FilterProcessor.ts"],"sourcesContent":["import type { VisualFilter } from './types';\n\n/**\n * FilterProcessor - Handles visual filters and effects\n * Single responsibility: Apply CSS filters and custom shader effects\n */\nexport class FilterProcessor {\n private filterCache = new Map<string, string>();\n\n /**\n * Apply filters to canvas context\n * Combines multiple filters into a single CSS filter string for performance\n */\n applyFilters(ctx: OffscreenCanvasRenderingContext2D, filters: VisualFilter[]): void {\n if (!filters || filters.length === 0) {\n ctx.filter = 'none';\n return;\n }\n\n // Generate cache key\n const cacheKey = this.generateCacheKey(filters);\n\n // Check cache\n let filterString = this.filterCache.get(cacheKey);\n\n if (!filterString) {\n filterString = this.buildFilterString(filters);\n this.filterCache.set(cacheKey, filterString);\n }\n\n ctx.filter = filterString;\n }\n\n /**\n * Build CSS filter string from filter array\n */\n private buildFilterString(filters: VisualFilter[]): string {\n const filterStrings: string[] = [];\n\n for (const filter of filters) {\n const filterStr = this.buildSingleFilter(filter);\n if (filterStr) {\n filterStrings.push(filterStr);\n }\n }\n\n return filterStrings.length > 0 ? filterStrings.join(' ') : 'none';\n }\n\n private buildSingleFilter(filter: VisualFilter): string | null {\n switch (filter.type) {\n case 'blur':\n return `blur(${filter.value ?? 0}px)`;\n\n case 'brightness':\n return `brightness(${filter.value ?? 1})`;\n\n case 'contrast':\n return `contrast(${filter.value ?? 1})`;\n\n case 'grayscale':\n return `grayscale(${filter.value ?? 0})`;\n\n case 'hue-rotate':\n return `hue-rotate(${filter.value ?? 0}deg)`;\n\n case 'saturate':\n return `saturate(${filter.value ?? 1})`;\n\n case 'sepia':\n return `sepia(${filter.value ?? 0})`;\n\n case 'custom':\n return this.buildCustomFilter(filter);\n\n default:\n console.warn(`Unknown filter type: ${filter.type}`);\n return null;\n }\n }\n\n /**\n * Build custom filter from params\n */\n private buildCustomFilter(filter: VisualFilter): string | null {\n if (!filter.params) return null;\n\n const { type, ...params } = filter.params;\n\n switch (type) {\n case 'drop-shadow':\n return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;\n\n case 'opacity':\n return `opacity(${params.value})`;\n\n case 'invert':\n return `invert(${params.value})`;\n\n default:\n return null;\n }\n }\n\n /**\n * Apply color matrix transformation for advanced effects\n * This allows for more complex color manipulations than CSS filters\n */\n applyColorMatrix(imageData: ImageData, matrix: number[]): ImageData {\n if (matrix.length !== 20) {\n throw new Error('Color matrix must have 20 values (4x5 matrix)');\n }\n\n const data = imageData.data;\n const length = data.length;\n\n for (let i = 0; i < length; i += 4) {\n const r = data[i]!;\n const g = data[i + 1]!;\n const b = data[i + 2]!;\n const a = data[i + 3]!;\n const m = matrix;\n\n // Apply matrix transformation\n data[i] = this.clamp(r * m[0]! + g * m[1]! + b * m[2]! + a * m[3]! + m[4]! * 255);\n data[i + 1] = this.clamp(r * m[5]! + g * m[6]! + b * m[7]! + a * m[8]! + m[9]! * 255);\n data[i + 2] = this.clamp(r * m[10]! + g * m[11]! + b * m[12]! + a * m[13]! + m[14]! * 255);\n data[i + 3] = this.clamp(r * m[15]! + g * m[16]! + b * m[17]! + a * m[18]! + m[19]! * 255);\n }\n\n return imageData;\n }\n\n /**\n * Predefined color matrices for common effects\n */\n getPresetMatrix(preset: string): number[] | null {\n switch (preset) {\n case 'vintage':\n return [\n 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0,\n 1, 0,\n ];\n\n case 'noir':\n return [\n 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0, 0, 0, 1, 0,\n ];\n\n case 'cool':\n return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];\n\n case 'warm':\n return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];\n\n default:\n return null;\n }\n }\n\n /**\n * Apply Gaussian blur manually (for cases where CSS filter is not enough)\n */\n applyGaussianBlur(imageData: ImageData, radius: number): ImageData {\n // Simplified box blur approximation of Gaussian blur\n const output = new ImageData(\n new Uint8ClampedArray(imageData.data),\n imageData.width,\n imageData.height\n );\n\n const width = imageData.width;\n const height = imageData.height;\n const data = imageData.data;\n const outData = output.data;\n\n // Horizontal pass\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dx = -radius; dx <= radius; dx++) {\n const nx = Math.min(Math.max(x + dx, 0), width - 1);\n const idx = (y * width + nx) * 4;\n r += data[idx]!;\n g += data[idx + 1]!;\n b += data[idx + 2]!;\n a += data[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n outData[idx] = r / count;\n outData[idx + 1] = g / count;\n outData[idx + 2] = b / count;\n outData[idx + 3] = a / count;\n }\n }\n\n // Vertical pass\n for (let x = 0; x < width; x++) {\n for (let y = 0; y < height; y++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dy = -radius; dy <= radius; dy++) {\n const ny = Math.min(Math.max(y + dy, 0), height - 1);\n const idx = (ny * width + x) * 4;\n r += outData[idx]!;\n g += outData[idx + 1]!;\n b += outData[idx + 2]!;\n a += outData[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n data[idx] = r / count;\n data[idx + 1] = g / count;\n data[idx + 2] = b / count;\n data[idx + 3] = a / count;\n }\n }\n\n return imageData;\n }\n\n private clamp(value: number): number {\n return Math.min(255, Math.max(0, Math.round(value)));\n }\n\n private generateCacheKey(filters: VisualFilter[]): string {\n return filters.map((f) => `${f.type}:${f.value ?? 'default'}`).join('|');\n }\n\n clearCache(): void {\n this.filterCache.clear();\n }\n\n getCacheSize(): number {\n return this.filterCache.size;\n }\n}\n"],"names":["idx"],"mappings":"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"}
|