nodejs-audio-visualizer 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +92 -0
- package/dist/config.js.map +1 -1
- package/dist/image.d.ts +26 -2
- package/dist/image.js +192 -1
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +37 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +107 -0
- package/src/image.ts +295 -26
- package/src/index.ts +86 -27
- package/src/tests/image.test.ts +86 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -11,6 +11,11 @@ export const defaults = {
|
|
|
11
11
|
spectrumRotation: 'mirror',
|
|
12
12
|
spectrumBusMargin: 12,
|
|
13
13
|
spectrumOpacity: '80%',
|
|
14
|
+
polarX: 'center',
|
|
15
|
+
polarY: 'middle',
|
|
16
|
+
polarInnerRadius: 100,
|
|
17
|
+
polarMaxBarLength: 160,
|
|
18
|
+
polarBarWidth: 15,
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
type RelativePositionValue = 0 | 0.5 | 1;
|
|
@@ -243,3 +248,105 @@ export const getFfmpeg_preset = (config: Config) =>
|
|
|
243
248
|
export const getFrame_processing_delay = (config: Config) =>
|
|
244
249
|
config.tweaks && config.tweaks.frame_processing_delay;
|
|
245
250
|
|
|
251
|
+
// Polar visualizer config getters
|
|
252
|
+
const getPolarX = (config: Config) =>
|
|
253
|
+
parseNumberIfPossible(
|
|
254
|
+
(config.outVideo.polar && config.outVideo.polar.x) ||
|
|
255
|
+
defaults.polarX
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const getPolarY = (config: Config) =>
|
|
259
|
+
parseNumberIfPossible(
|
|
260
|
+
(config.outVideo.polar && config.outVideo.polar.y) ||
|
|
261
|
+
defaults.polarY
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
export const getPolarXAbsolute = (
|
|
265
|
+
config: Config,
|
|
266
|
+
backgroundImageWidth: number
|
|
267
|
+
) => {
|
|
268
|
+
const polarX = getPolarX(config);
|
|
269
|
+
if (typeof polarX !== 'string') {
|
|
270
|
+
return polarX;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const polarXParsed = parsePositionAlias(polarX);
|
|
274
|
+
if (polarXParsed.value === 0) {
|
|
275
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
276
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
277
|
+
return polarRadius + polarLength;
|
|
278
|
+
}
|
|
279
|
+
if (polarXParsed.value === 1) {
|
|
280
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
281
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
282
|
+
return backgroundImageWidth - polarRadius - polarLength;
|
|
283
|
+
}
|
|
284
|
+
return polarXParsed.value * backgroundImageWidth;
|
|
285
|
+
} catch {
|
|
286
|
+
throw new Error(`Invalid polar x value: ${polarX}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const getPolarYAbsolute = (
|
|
291
|
+
config: Config,
|
|
292
|
+
backgroundImageHeight: number
|
|
293
|
+
) => {
|
|
294
|
+
const polarY = getPolarY(config);
|
|
295
|
+
if (typeof polarY !== 'string') {
|
|
296
|
+
return polarY;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const polarYParsed = parsePositionAlias(polarY);
|
|
300
|
+
if (polarYParsed.value === 0) {
|
|
301
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
302
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
303
|
+
return polarRadius + polarLength;
|
|
304
|
+
}
|
|
305
|
+
if (polarYParsed.value === 1) {
|
|
306
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
307
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
308
|
+
return backgroundImageHeight - polarRadius - polarLength;
|
|
309
|
+
}
|
|
310
|
+
return polarYParsed.value * backgroundImageHeight;
|
|
311
|
+
} catch {
|
|
312
|
+
throw new Error(`Invalid polar y value: ${polarY}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export const getPolarInnerRadius = (config: Config) =>
|
|
317
|
+
(config.outVideo.polar && config.outVideo.polar.innerRadius) ||
|
|
318
|
+
defaults.polarInnerRadius;
|
|
319
|
+
|
|
320
|
+
export const getPolarMaxBarLength = (config: Config) =>
|
|
321
|
+
(config.outVideo.polar && config.outVideo.polar.maxBarLength) ||
|
|
322
|
+
defaults.polarMaxBarLength;
|
|
323
|
+
|
|
324
|
+
export const getPolarBarWidth = (config: Config) =>
|
|
325
|
+
(config.outVideo.polar && config.outVideo.polar.barWidth) ||
|
|
326
|
+
defaults.polarBarWidth;
|
|
327
|
+
|
|
328
|
+
export const getPolarEffect = (config: Config) =>
|
|
329
|
+
config.outVideo.polar?.effect;
|
|
330
|
+
|
|
331
|
+
export const getPolarColor = (config: Config) =>
|
|
332
|
+
config.outVideo.polar && config.outVideo.polar.color;
|
|
333
|
+
|
|
334
|
+
export const getPolarOpacity = (config: Config) =>
|
|
335
|
+
(config.outVideo.polar && config.outVideo.polar.opacity) ||
|
|
336
|
+
defaults.spectrumOpacity;
|
|
337
|
+
|
|
338
|
+
export const getPolarOpacityParsed = (config: Config) => {
|
|
339
|
+
const polarOpacity = getPolarOpacity(config);
|
|
340
|
+
if (!checkIsPercentValue(polarOpacity)) {
|
|
341
|
+
throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Use string percent value, for example '80%'.`);
|
|
342
|
+
}
|
|
343
|
+
const percentValue = parseInt(polarOpacity);
|
|
344
|
+
if (
|
|
345
|
+
(percentValue < 0) ||
|
|
346
|
+
(percentValue > 100)
|
|
347
|
+
) {
|
|
348
|
+
throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Percent values must be in range from '0%' to '100%'.`);
|
|
349
|
+
}
|
|
350
|
+
return percentValue / 100;
|
|
351
|
+
};
|
|
352
|
+
|
package/src/image.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface MixColorProps {
|
|
|
26
26
|
backgroundColor: Color;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export const mixValues =(value1: number, value1Opacity: number, value2: number) =>
|
|
29
|
+
export const mixValues = (value1: number, value1Opacity: number, value2: number) =>
|
|
30
30
|
Math.round(value2 * (1 - value1Opacity) + value1 * value1Opacity);
|
|
31
31
|
|
|
32
32
|
export const mixColors = ({
|
|
@@ -101,6 +101,126 @@ export const drawRect = ({
|
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
interface Edge {
|
|
105
|
+
yMin: number;
|
|
106
|
+
yMax: number;
|
|
107
|
+
x: number;
|
|
108
|
+
dx: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface FillRectProps {
|
|
112
|
+
imageDstBuffer: EncodedBmp;
|
|
113
|
+
points: [Position, Position, Position, Position];
|
|
114
|
+
color: Color;
|
|
115
|
+
opacity: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const buildQuadEdgeTable = (points: [Position, Position, Position, Position]): Edge[] => {
|
|
119
|
+
const edges: Edge[] = [];
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < points.length; i++) {
|
|
122
|
+
const p1 = points[i];
|
|
123
|
+
const p2 = points[(i + 1) % points.length];
|
|
124
|
+
|
|
125
|
+
if (p1.y === p2.y) continue; // Skip horizontal edges
|
|
126
|
+
|
|
127
|
+
const yMin = Math.min(p1.y, p2.y);
|
|
128
|
+
const yMax = Math.max(p1.y, p2.y);
|
|
129
|
+
const x = p1.y < p2.y ? p1.x : p2.x;
|
|
130
|
+
const dx = (p2.x - p1.x) / (p2.y - p1.y);
|
|
131
|
+
|
|
132
|
+
edges.push({ yMin, yMax, x, dx });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return edges.sort((a, b) => a.yMin - b.yMin);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fills a quadrilateral (4-sided polygon) using scan-line algorithm
|
|
140
|
+
* Only the outer edges are anti-aliased
|
|
141
|
+
*/
|
|
142
|
+
export const fillRect = ({
|
|
143
|
+
imageDstBuffer,
|
|
144
|
+
points,
|
|
145
|
+
color,
|
|
146
|
+
opacity,
|
|
147
|
+
}: FillRectProps) => {
|
|
148
|
+
// Build edge table from all 4 vertices
|
|
149
|
+
const edgeTable = buildQuadEdgeTable(points);
|
|
150
|
+
if (edgeTable.length === 0) return;
|
|
151
|
+
|
|
152
|
+
// Find the bounding box
|
|
153
|
+
const yMin = Math.min(...points.map(p => p.y));
|
|
154
|
+
const yMax = Math.max(...points.map(p => p.y));
|
|
155
|
+
|
|
156
|
+
// Active Edge Table for scan line algorithm
|
|
157
|
+
const activeEdges: Edge[] = [];
|
|
158
|
+
|
|
159
|
+
// Scan line algorithm: process each horizontal line
|
|
160
|
+
for (let y = Math.floor(yMin); y <= Math.ceil(yMax); y++) {
|
|
161
|
+
// Add edges that start at this scanline to AET
|
|
162
|
+
for (const edge of edgeTable) {
|
|
163
|
+
if (Math.floor(edge.yMin) === y) {
|
|
164
|
+
activeEdges.push({ ...edge });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Remove edges that end at this scanline from AET
|
|
169
|
+
for (let i = activeEdges.length - 1; i >= 0; i--) {
|
|
170
|
+
if (Math.ceil(activeEdges[i].yMax) <= y) {
|
|
171
|
+
activeEdges.splice(i, 1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Sort active edges by x coordinate
|
|
176
|
+
activeEdges.sort((a, b) => a.x - b.x);
|
|
177
|
+
|
|
178
|
+
// Fill pixels between pairs of intersections
|
|
179
|
+
for (let i = 0; i < activeEdges.length; i += 2) {
|
|
180
|
+
if (i + 1 < activeEdges.length) {
|
|
181
|
+
const xStart = activeEdges[i].x;
|
|
182
|
+
const xEnd = activeEdges[i + 1].x;
|
|
183
|
+
|
|
184
|
+
// Anti-aliased rendering with sub-pixel precision
|
|
185
|
+
const xStartFloor = Math.floor(xStart);
|
|
186
|
+
const xEndCeil = Math.ceil(xEnd);
|
|
187
|
+
|
|
188
|
+
for (let x = xStartFloor; x < xEndCeil; x++) {
|
|
189
|
+
const pixelIndex = imageDstBuffer.shiftPos + y * imageDstBuffer.rowBytes + x * 3;
|
|
190
|
+
|
|
191
|
+
// Calculate coverage for anti-aliasing
|
|
192
|
+
let coverage = 1.0;
|
|
193
|
+
if (x === xStartFloor) {
|
|
194
|
+
// Left edge pixel - calculate fractional coverage
|
|
195
|
+
coverage = Math.min(1.0, 1.0 - (xStart - xStartFloor));
|
|
196
|
+
} else if (x === xEndCeil - 1) {
|
|
197
|
+
// Right edge pixel - calculate fractional coverage
|
|
198
|
+
coverage = Math.min(1.0, xEnd - Math.floor(xEnd));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Apply coverage to opacity for smooth edges
|
|
202
|
+
const effectiveOpacity = opacity * coverage;
|
|
203
|
+
|
|
204
|
+
const pixelColor = getRectPixelColor({
|
|
205
|
+
imageDstBuffer,
|
|
206
|
+
pixelIndex,
|
|
207
|
+
color,
|
|
208
|
+
opacity: effectiveOpacity,
|
|
209
|
+
});
|
|
210
|
+
imageDstBuffer.data[pixelIndex] = pixelColor.blue;
|
|
211
|
+
imageDstBuffer.data[pixelIndex + 1] = pixelColor.green;
|
|
212
|
+
imageDstBuffer.data[pixelIndex + 2] = pixelColor.red;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update x coordinates for next scanline (x' = x + dx)
|
|
218
|
+
for (const edge of activeEdges) {
|
|
219
|
+
edge.x += edge.dx;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
104
224
|
interface DrawSpectrumProps {
|
|
105
225
|
imageDstBuffer: EncodedBmp;
|
|
106
226
|
spectrum: number[];
|
|
@@ -167,9 +287,79 @@ const drawSpectrum = ({
|
|
|
167
287
|
}
|
|
168
288
|
};
|
|
169
289
|
|
|
170
|
-
|
|
290
|
+
const calcCirclePoint = (angleRadians: number, radius: number): Position => {
|
|
291
|
+
const circleX = radius * Math.cos(angleRadians);
|
|
292
|
+
const circleY = radius * Math.sin(angleRadians);
|
|
293
|
+
return { x: circleX, y: circleY };
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const lerp = (start: number, end: number, t: number) =>
|
|
297
|
+
start + (end - start) * t;
|
|
298
|
+
|
|
299
|
+
interface DrawPolarSpectrumProps {
|
|
300
|
+
imageDstBuffer: EncodedBmp;
|
|
301
|
+
spectrum: number[];
|
|
302
|
+
centerX: number;
|
|
303
|
+
centerY: number;
|
|
304
|
+
innerRadius: number;
|
|
305
|
+
maxBarLength: number;
|
|
306
|
+
barWidth: number;
|
|
307
|
+
color: Color;
|
|
308
|
+
opacity: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const drawPolarSpectrum = ({
|
|
312
|
+
imageDstBuffer,
|
|
313
|
+
spectrum,
|
|
314
|
+
centerX,
|
|
315
|
+
centerY,
|
|
316
|
+
innerRadius,
|
|
317
|
+
maxBarLength,
|
|
318
|
+
barWidth,
|
|
319
|
+
color,
|
|
320
|
+
opacity,
|
|
321
|
+
}: DrawPolarSpectrumProps) => {
|
|
322
|
+
const angleStep = (2 * Math.PI) / spectrum.length;
|
|
323
|
+
const innerLength = innerRadius * 2 * Math.PI;
|
|
324
|
+
const radiansPerPixel = 2 * Math.PI / innerLength;
|
|
325
|
+
const barWidthInRadians = radiansPerPixel * barWidth;
|
|
326
|
+
const spectrumAverage = spectrum.reduce((a, b) => a + b, 0) / spectrum.length;
|
|
327
|
+
const scaleFactor = lerp(0.75, 1, spectrumAverage);
|
|
328
|
+
const scaledMaxOuterRadius = (innerRadius + maxBarLength) * scaleFactor;
|
|
329
|
+
const scaledInnerRadius = innerRadius * scaleFactor;
|
|
330
|
+
for (let i = 0; i < spectrum.length; i++) {
|
|
331
|
+
const currentSpectrumValue = spectrum[i] || 0;
|
|
332
|
+
const busRadius = lerp(scaledInnerRadius, scaledMaxOuterRadius, currentSpectrumValue);
|
|
333
|
+
const angle = i * angleStep;
|
|
334
|
+
const angleRadiansStart = angle;
|
|
335
|
+
const angleRadiansEnd = angle + barWidthInRadians;
|
|
336
|
+
|
|
337
|
+
const point1 = calcCirclePoint(angleRadiansStart, scaledInnerRadius);
|
|
338
|
+
const point2 = calcCirclePoint(angleRadiansStart, busRadius);
|
|
339
|
+
|
|
340
|
+
const point3 = calcCirclePoint(angleRadiansEnd, scaledInnerRadius);
|
|
341
|
+
const point4 = calcCirclePoint(angleRadiansEnd, busRadius);
|
|
342
|
+
|
|
343
|
+
fillRect({
|
|
344
|
+
imageDstBuffer,
|
|
345
|
+
points: [
|
|
346
|
+
{ x: centerX + Math.round(point1.x), y: centerY + Math.round(point1.y) },
|
|
347
|
+
{ x: centerX + Math.round(point2.x), y: centerY + Math.round(point2.y) },
|
|
348
|
+
{ x: centerX + Math.round(point4.x), y: centerY + Math.round(point4.y) },
|
|
349
|
+
{ x: centerX + Math.round(point3.x), y: centerY + Math.round(point3.y) },
|
|
350
|
+
],
|
|
351
|
+
color,
|
|
352
|
+
opacity,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export interface CommonVisualizerFrameProps {
|
|
171
358
|
backgroundImageBuffer: EncodedBmp;
|
|
172
359
|
spectrum: number[];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export interface CreateVisualizerFrameProps {
|
|
173
363
|
size: Size;
|
|
174
364
|
position: Position;
|
|
175
365
|
rotation: RotationAliasName;
|
|
@@ -179,7 +369,18 @@ interface CreateVisualizerFrameProps {
|
|
|
179
369
|
spectrumEffect?: SpectrumEffect;
|
|
180
370
|
}
|
|
181
371
|
|
|
182
|
-
export
|
|
372
|
+
export interface CreatePolarVisualizerFrameProps {
|
|
373
|
+
centerX: number;
|
|
374
|
+
centerY: number;
|
|
375
|
+
innerRadius: number;
|
|
376
|
+
maxBarLength: number;
|
|
377
|
+
barWidth: number;
|
|
378
|
+
color: Color | string;
|
|
379
|
+
opacity: number;
|
|
380
|
+
spectrumEffect?: SpectrumEffect;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export const createSpectrumVisualizerFrameGenerator = () => {
|
|
183
384
|
let prevSpectrum: number[] | null = null;
|
|
184
385
|
|
|
185
386
|
return ({
|
|
@@ -192,35 +393,34 @@ export const createVisualizerFrameGenerator = () => {
|
|
|
192
393
|
color,
|
|
193
394
|
opacity,
|
|
194
395
|
spectrumEffect,
|
|
195
|
-
}: CreateVisualizerFrameProps) => {
|
|
396
|
+
}: CommonVisualizerFrameProps & CreateVisualizerFrameProps) => {
|
|
196
397
|
const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
197
398
|
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
198
|
-
|
|
199
|
-
|
|
399
|
+
|
|
200
400
|
const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
201
401
|
if (spectrumEffect === 'volume') {
|
|
202
402
|
drawSpectrum({
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
403
|
+
imageDstBuffer,
|
|
404
|
+
spectrum,
|
|
405
|
+
size,
|
|
406
|
+
position: { x: position.x + 2, y: position.y + 2 },
|
|
407
|
+
rotation,
|
|
408
|
+
margin,
|
|
409
|
+
color: rgbSpectrumColor,
|
|
410
|
+
opacity: opacity * 0.5,
|
|
411
|
+
});
|
|
212
412
|
}
|
|
213
413
|
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
214
414
|
drawSpectrum({
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
415
|
+
imageDstBuffer,
|
|
416
|
+
spectrum: prevSpectrum,
|
|
417
|
+
size,
|
|
418
|
+
position,
|
|
419
|
+
rotation,
|
|
420
|
+
margin,
|
|
421
|
+
color: rgbSpectrumColor,
|
|
422
|
+
opacity: opacity * 0.3,
|
|
423
|
+
});
|
|
224
424
|
}
|
|
225
425
|
drawSpectrum({
|
|
226
426
|
imageDstBuffer,
|
|
@@ -232,9 +432,78 @@ export const createVisualizerFrameGenerator = () => {
|
|
|
232
432
|
color: rgbSpectrumColor,
|
|
233
433
|
opacity,
|
|
234
434
|
});
|
|
235
|
-
|
|
435
|
+
|
|
236
436
|
prevSpectrum = spectrum;
|
|
237
|
-
|
|
437
|
+
|
|
438
|
+
return imageDstBuffer;
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
export const createPolarVisualizerFrameGenerator = () => {
|
|
443
|
+
let prevSpectrum: number[] | null = null;
|
|
444
|
+
|
|
445
|
+
return ({
|
|
446
|
+
backgroundImageBuffer,
|
|
447
|
+
spectrum,
|
|
448
|
+
centerX,
|
|
449
|
+
centerY,
|
|
450
|
+
innerRadius,
|
|
451
|
+
maxBarLength,
|
|
452
|
+
barWidth,
|
|
453
|
+
color,
|
|
454
|
+
opacity,
|
|
455
|
+
spectrumEffect,
|
|
456
|
+
}: CommonVisualizerFrameProps & CreatePolarVisualizerFrameProps) => {
|
|
457
|
+
const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
458
|
+
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
459
|
+
|
|
460
|
+
const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
461
|
+
|
|
462
|
+
// Draw shadow/volume effect
|
|
463
|
+
if (spectrumEffect === 'volume') {
|
|
464
|
+
drawPolarSpectrum({
|
|
465
|
+
imageDstBuffer,
|
|
466
|
+
spectrum,
|
|
467
|
+
centerX: centerX + 2,
|
|
468
|
+
centerY: centerY + 2,
|
|
469
|
+
innerRadius,
|
|
470
|
+
maxBarLength,
|
|
471
|
+
barWidth,
|
|
472
|
+
color: rgbSpectrumColor,
|
|
473
|
+
opacity: opacity * 0.5,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Draw smooth/trail effect with previous frame
|
|
478
|
+
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
479
|
+
drawPolarSpectrum({
|
|
480
|
+
imageDstBuffer,
|
|
481
|
+
spectrum: prevSpectrum,
|
|
482
|
+
centerX,
|
|
483
|
+
centerY,
|
|
484
|
+
innerRadius,
|
|
485
|
+
maxBarLength,
|
|
486
|
+
barWidth,
|
|
487
|
+
color: rgbSpectrumColor,
|
|
488
|
+
opacity: opacity * 0.3,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Draw main spectrum
|
|
493
|
+
drawPolarSpectrum({
|
|
494
|
+
imageDstBuffer,
|
|
495
|
+
spectrum,
|
|
496
|
+
centerX,
|
|
497
|
+
centerY,
|
|
498
|
+
innerRadius,
|
|
499
|
+
maxBarLength,
|
|
500
|
+
barWidth,
|
|
501
|
+
color: rgbSpectrumColor,
|
|
502
|
+
opacity,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
prevSpectrum = spectrum;
|
|
506
|
+
|
|
238
507
|
return imageDstBuffer;
|
|
239
508
|
};
|
|
240
509
|
};
|
package/src/index.ts
CHANGED
|
@@ -17,11 +17,20 @@ import {
|
|
|
17
17
|
getSpectrumRotation,
|
|
18
18
|
getSpectrumEffect,
|
|
19
19
|
SpectrumEffect,
|
|
20
|
+
getPolarXAbsolute,
|
|
21
|
+
getPolarYAbsolute,
|
|
22
|
+
getPolarInnerRadius,
|
|
23
|
+
getPolarMaxBarLength,
|
|
24
|
+
getPolarBarWidth,
|
|
25
|
+
getPolarEffect,
|
|
26
|
+
getPolarColor,
|
|
27
|
+
getPolarOpacityParsed,
|
|
20
28
|
} from './config';
|
|
21
29
|
import { createAudioBuffer, bufferToUInt8, createSpectrumsProcessor } from './audio';
|
|
22
|
-
import { parseImage, getImageColor, invertColor, Color, convertToBmp,
|
|
30
|
+
import { parseImage, getImageColor, invertColor, Color, convertToBmp, createSpectrumVisualizerFrameGenerator, createPolarVisualizerFrameGenerator, CreatePolarVisualizerFrameProps, CreateVisualizerFrameProps, CommonVisualizerFrameProps } from './image';
|
|
23
31
|
import { spawnFfmpegVideoWriter, getProgress, calculateProgress, waitDrain } from './video';
|
|
24
|
-
import { createBpmEncoder } from './bpmEncoder';
|
|
32
|
+
import { createBpmEncoder, EncodedBmp } from './bpmEncoder';
|
|
33
|
+
import { BmpDecoder } from 'bmp-js';
|
|
25
34
|
|
|
26
35
|
export const PCM_FORMAT = {
|
|
27
36
|
bit: 8,
|
|
@@ -50,6 +59,16 @@ export interface Config {
|
|
|
50
59
|
effect?: SpectrumEffect;
|
|
51
60
|
color?: Color | string;
|
|
52
61
|
opacity?: string;
|
|
62
|
+
};
|
|
63
|
+
polar?: {
|
|
64
|
+
x?: number | PositionAliasName;
|
|
65
|
+
y?: number | PositionAliasName;
|
|
66
|
+
innerRadius?: number;
|
|
67
|
+
maxBarLength?: number;
|
|
68
|
+
barWidth?: number;
|
|
69
|
+
effect?: SpectrumEffect;
|
|
70
|
+
color?: Color | string;
|
|
71
|
+
opacity?: string;
|
|
53
72
|
}
|
|
54
73
|
};
|
|
55
74
|
tweaks?: {
|
|
@@ -74,8 +93,67 @@ export type RotationAliasName = typeof rotationAliasValues[number];
|
|
|
74
93
|
const sleep = (timeout: number) =>
|
|
75
94
|
new Promise(resolve => setTimeout(resolve, timeout));
|
|
76
95
|
|
|
96
|
+
const createVisualizerFrameGenerator = (
|
|
97
|
+
config: Config,
|
|
98
|
+
backgroundImage: BmpDecoder,
|
|
99
|
+
spectrumBusMargin: number
|
|
100
|
+
): (params: CommonVisualizerFrameProps) => EncodedBmp => {
|
|
101
|
+
if (!config.outVideo.spectrum) {
|
|
102
|
+
const createPolarVisualizerFrame = createPolarVisualizerFrameGenerator();
|
|
103
|
+
const polarX = getPolarXAbsolute(config, backgroundImage.width);
|
|
104
|
+
const polarY = getPolarYAbsolute(config, backgroundImage.height);
|
|
105
|
+
const polarInnerRadius = getPolarInnerRadius(config);
|
|
106
|
+
const polarMaxBarLength = getPolarMaxBarLength(config);
|
|
107
|
+
const polarBarWidth = getPolarBarWidth(config);
|
|
108
|
+
const polarColor = getPolarColor(config) || invertColor(getImageColor(backgroundImage));
|
|
109
|
+
const polarEffect = getPolarEffect(config);
|
|
110
|
+
const polarOpacity = getPolarOpacityParsed(config);
|
|
111
|
+
|
|
112
|
+
return (params: CommonVisualizerFrameProps) => {
|
|
113
|
+
return createPolarVisualizerFrame({
|
|
114
|
+
...params,
|
|
115
|
+
centerX: polarX,
|
|
116
|
+
centerY: polarY,
|
|
117
|
+
innerRadius: polarInnerRadius,
|
|
118
|
+
maxBarLength: polarMaxBarLength,
|
|
119
|
+
barWidth: polarBarWidth,
|
|
120
|
+
color: polarColor,
|
|
121
|
+
opacity: polarOpacity,
|
|
122
|
+
spectrumEffect: polarEffect,
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const createPolarVisualizerFrame = createSpectrumVisualizerFrameGenerator();
|
|
127
|
+
const spectrumWidth = getSpectrumWidthAbsolute(config, backgroundImage.width);
|
|
128
|
+
const spectrumHeight = getSpectrumHeightAbsolute(config, backgroundImage.height);
|
|
129
|
+
const spectrumX = getSpectrumXAbsolute(config, spectrumWidth, backgroundImage.width);
|
|
130
|
+
const spectrumY = getSpectrumYAbsolute(config, spectrumHeight, backgroundImage.height);
|
|
131
|
+
const spectrumRotation = getSpectrumRotation(config);
|
|
132
|
+
const spectrumColor = getSpectrumColor(config) || invertColor(getImageColor(backgroundImage));
|
|
133
|
+
const spectrumEffect = getSpectrumEffect(config);
|
|
134
|
+
const spectrumOpacity = getSpectrumOpacityParsed(config);
|
|
135
|
+
|
|
136
|
+
return (params: CommonVisualizerFrameProps) => {
|
|
137
|
+
return createPolarVisualizerFrame({
|
|
138
|
+
...params,
|
|
139
|
+
size: { width: spectrumWidth, height: spectrumHeight },
|
|
140
|
+
position: { x: spectrumX, y: spectrumY },
|
|
141
|
+
rotation: spectrumRotation,
|
|
142
|
+
margin: spectrumBusMargin,
|
|
143
|
+
color: spectrumColor,
|
|
144
|
+
opacity: spectrumOpacity,
|
|
145
|
+
spectrumEffect,
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
77
150
|
export const renderAudioVisualizer = (config: Config, onProgress?: (progress: number) => any, shouldStop?: () => boolean) =>
|
|
78
151
|
new Promise<number>(async (resolve) => {
|
|
152
|
+
// Validate that only one visualizer type is specified
|
|
153
|
+
if (config.outVideo.spectrum && config.outVideo.polar) {
|
|
154
|
+
throw new Error('Cannot use both "spectrum" and "polar" options. Please specify only one visualizer type.');
|
|
155
|
+
}
|
|
156
|
+
|
|
79
157
|
const audioFilePath = getAudioFilePath(config);
|
|
80
158
|
const backgroundImagePath = getBackgroundImagePath(config);
|
|
81
159
|
const outVideoPath = getOutVideoPath(config);
|
|
@@ -91,25 +169,12 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
|
|
|
91
169
|
|
|
92
170
|
const spectrumBusMargin = getSpectrumBusMargin();
|
|
93
171
|
const FPS = getFPS(config);
|
|
94
|
-
const spectrumWidth =
|
|
95
|
-
getSpectrumWidthAbsolute(config, backgroundImage.width);
|
|
96
|
-
const spectrumHeight =
|
|
97
|
-
getSpectrumHeightAbsolute(config, backgroundImage.height);
|
|
98
|
-
const spectrumX =
|
|
99
|
-
getSpectrumXAbsolute(config, spectrumWidth, backgroundImage.width);
|
|
100
|
-
const spectrumY =
|
|
101
|
-
getSpectrumYAbsolute(config, spectrumHeight, backgroundImage.height);
|
|
102
|
-
const spectrumRotation =
|
|
103
|
-
getSpectrumRotation(config);
|
|
104
|
-
const spectrumColor =
|
|
105
|
-
getSpectrumColor(config) ||
|
|
106
|
-
invertColor(getImageColor(backgroundImage));
|
|
107
|
-
const spectrumEffect = getSpectrumEffect(config);
|
|
108
|
-
const spectrumOpacity = getSpectrumOpacityParsed(config);
|
|
109
172
|
const ffmpeg_cfr = getFfmpeg_cfr(config);
|
|
110
173
|
const ffmpeg_preset = getFfmpeg_preset(config);
|
|
111
174
|
const frame_processing_delay = getFrame_processing_delay(config);
|
|
112
175
|
|
|
176
|
+
const createVisualizerFrame = createVisualizerFrameGenerator(config, backgroundImage, spectrumBusMargin);
|
|
177
|
+
|
|
113
178
|
const audioDuration = audioBuffer.length / sampleRate;
|
|
114
179
|
const framesCount = Math.trunc(audioDuration * FPS);
|
|
115
180
|
const audioDataStep = Math.trunc(audioBuffer.length / framesCount);
|
|
@@ -129,7 +194,7 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
|
|
|
129
194
|
const backgroundImageBuffer = bpmEncoder(backgroundImage.data);
|
|
130
195
|
const skipFramesCount = FPS < 45 ? 1 : 2;
|
|
131
196
|
const processSpectrum = createSpectrumsProcessor(sampleRate, skipFramesCount);
|
|
132
|
-
|
|
197
|
+
|
|
133
198
|
for (let i = 0; i < framesCount; i++) {
|
|
134
199
|
const currentFrameData = PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
|
|
135
200
|
processingBuffer.copyWithin(0, currentFrameData.length);
|
|
@@ -137,17 +202,11 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
|
|
|
137
202
|
|
|
138
203
|
const audioDataParser = () => Array.from(processingBuffer);
|
|
139
204
|
const spectrum = processSpectrum(audioDataParser);
|
|
140
|
-
const
|
|
205
|
+
const commonVisualizerFrameProps: CommonVisualizerFrameProps = {
|
|
141
206
|
backgroundImageBuffer,
|
|
142
207
|
spectrum,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
rotation: spectrumRotation,
|
|
146
|
-
margin: spectrumBusMargin,
|
|
147
|
-
color: spectrumColor,
|
|
148
|
-
opacity: spectrumOpacity,
|
|
149
|
-
spectrumEffect,
|
|
150
|
-
});
|
|
208
|
+
};
|
|
209
|
+
const frameImage = createVisualizerFrame(commonVisualizerFrameProps);
|
|
151
210
|
const isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
|
|
152
211
|
if (!isFrameProcessed) {
|
|
153
212
|
await waitDrain(ffmpegVideoWriter.stdin);
|