nodejs-audio-visualizer 2.1.4 → 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/src/image.ts CHANGED
@@ -2,6 +2,7 @@ import { RotationAliasName } from './index';
2
2
  import { decode, BmpDecoder } from 'bmp-js';
3
3
  import { EncodedBmp } from './bpmEncoder';
4
4
  import Jimp from 'jimp';
5
+ import { SpectrumEffect } from './config';
5
6
 
6
7
  export interface Color {
7
8
  red: number;
@@ -25,7 +26,7 @@ interface MixColorProps {
25
26
  backgroundColor: Color;
26
27
  }
27
28
 
28
- export const mixValues =(value1: number, value1Opacity: number, value2: number) =>
29
+ export const mixValues = (value1: number, value1Opacity: number, value2: number) =>
29
30
  Math.round(value2 * (1 - value1Opacity) + value1 * value1Opacity);
30
31
 
31
32
  export const mixColors = ({
@@ -100,6 +101,126 @@ export const drawRect = ({
100
101
  }
101
102
  };
102
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
+
103
224
  interface DrawSpectrumProps {
104
225
  imageDstBuffer: EncodedBmp;
105
226
  spectrum: number[];
@@ -111,6 +232,27 @@ interface DrawSpectrumProps {
111
232
  opacity: number;
112
233
  }
113
234
 
235
+ const getRectY = (rotation: RotationAliasName, top: number, height: number, rectHeight: number) => {
236
+ switch (rotation) {
237
+ case 'up':
238
+ return top + height - rectHeight;
239
+ case 'mirror':
240
+ return top + Math.trunc((height - rectHeight) / 2);
241
+ default:
242
+ return top;
243
+ }
244
+ };
245
+
246
+ const minRectHeight = 2;
247
+
248
+ const getRectHeight = (height: number, spectrumValue: number) => {
249
+ const rectHeight = Math.trunc(height * spectrumValue);
250
+ if (rectHeight < minRectHeight) {
251
+ return minRectHeight;
252
+ }
253
+ return rectHeight;
254
+ };
255
+
114
256
  const drawSpectrum = ({
115
257
  imageDstBuffer,
116
258
  spectrum,
@@ -133,8 +275,8 @@ const drawSpectrum = ({
133
275
  }
134
276
 
135
277
  const rectX = left + busWidth * spectrumX;
136
- const rectHeight = Math.trunc(height * spectrumValue);
137
- const rectY = (rotation === 'up') ? top + height - rectHeight : top;
278
+ const rectHeight = getRectHeight(height, spectrumValue);
279
+ const rectY = getRectY(rotation, top, height, rectHeight);
138
280
  drawRect({
139
281
  imageDstBuffer,
140
282
  position: { x: rectX + margin, y: rectY },
@@ -145,42 +287,225 @@ const drawSpectrum = ({
145
287
  }
146
288
  };
147
289
 
148
- interface CreateVisualizerFrameProps {
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 {
149
358
  backgroundImageBuffer: EncodedBmp;
150
359
  spectrum: number[];
360
+ }
361
+
362
+ export interface CreateVisualizerFrameProps {
151
363
  size: Size;
152
364
  position: Position;
153
365
  rotation: RotationAliasName;
154
366
  margin: number;
155
367
  color: Color | string;
156
368
  opacity: number;
369
+ spectrumEffect?: SpectrumEffect;
157
370
  }
158
371
 
159
- export const createVisualizerFrame = ({
160
- backgroundImageBuffer,
161
- spectrum,
162
- size,
163
- position,
164
- rotation,
165
- margin,
166
- color,
167
- opacity,
168
- }: CreateVisualizerFrameProps) => {
169
- const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
170
- imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
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
+ }
171
382
 
172
- const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
173
- drawSpectrum({
174
- imageDstBuffer,
383
+ export const createSpectrumVisualizerFrameGenerator = () => {
384
+ let prevSpectrum: number[] | null = null;
385
+
386
+ return ({
387
+ backgroundImageBuffer,
175
388
  spectrum,
176
389
  size,
177
390
  position,
178
391
  rotation,
179
392
  margin,
180
- color: rgbSpectrumColor,
393
+ color,
181
394
  opacity,
182
- });
183
- return imageDstBuffer;
395
+ spectrumEffect,
396
+ }: CommonVisualizerFrameProps & CreateVisualizerFrameProps) => {
397
+ const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
398
+ imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
399
+
400
+ const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
401
+ if (spectrumEffect === 'volume') {
402
+ drawSpectrum({
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
+ });
412
+ }
413
+ if (spectrumEffect === 'smooth' && prevSpectrum) {
414
+ drawSpectrum({
415
+ imageDstBuffer,
416
+ spectrum: prevSpectrum,
417
+ size,
418
+ position,
419
+ rotation,
420
+ margin,
421
+ color: rgbSpectrumColor,
422
+ opacity: opacity * 0.3,
423
+ });
424
+ }
425
+ drawSpectrum({
426
+ imageDstBuffer,
427
+ spectrum,
428
+ size,
429
+ position,
430
+ rotation,
431
+ margin,
432
+ color: rgbSpectrumColor,
433
+ opacity,
434
+ });
435
+
436
+ prevSpectrum = spectrum;
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
+
507
+ return imageDstBuffer;
508
+ };
184
509
  };
185
510
 
186
511
  export const convertToBmp = async (filePath: string) =>
package/src/index.ts CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  getBackgroundImagePath,
4
4
  getOutVideoPath,
5
5
  getFPS,
6
- getSpectrumBusesCount,
7
6
  getSpectrumBusMargin,
8
7
  getSpectrumWidthAbsolute,
9
8
  getSpectrumHeightAbsolute,
@@ -15,12 +14,23 @@ import {
15
14
  getFfmpeg_preset,
16
15
  getFrame_processing_delay,
17
16
  rotationAliasValues,
18
- getSpectrumRotation
17
+ getSpectrumRotation,
18
+ getSpectrumEffect,
19
+ SpectrumEffect,
20
+ getPolarXAbsolute,
21
+ getPolarYAbsolute,
22
+ getPolarInnerRadius,
23
+ getPolarMaxBarLength,
24
+ getPolarBarWidth,
25
+ getPolarEffect,
26
+ getPolarColor,
27
+ getPolarOpacityParsed,
19
28
  } from './config';
20
29
  import { createAudioBuffer, bufferToUInt8, createSpectrumsProcessor } from './audio';
21
- import { parseImage, createVisualizerFrame, getImageColor, invertColor, Color, convertToBmp } from './image';
30
+ import { parseImage, getImageColor, invertColor, Color, convertToBmp, createSpectrumVisualizerFrameGenerator, createPolarVisualizerFrameGenerator, CreatePolarVisualizerFrameProps, CreateVisualizerFrameProps, CommonVisualizerFrameProps } from './image';
22
31
  import { spawnFfmpegVideoWriter, getProgress, calculateProgress, waitDrain } from './video';
23
- import { createBpmEncoder } from './bpmEncoder';
32
+ import { createBpmEncoder, EncodedBmp } from './bpmEncoder';
33
+ import { BmpDecoder } from 'bmp-js';
24
34
 
25
35
  export const PCM_FORMAT = {
26
36
  bit: 8,
@@ -28,6 +38,7 @@ export const PCM_FORMAT = {
28
38
  parseFunction: bufferToUInt8
29
39
  };
30
40
  const FFMPEG_FORMAT = `${PCM_FORMAT.sign}${PCM_FORMAT.bit}`;
41
+ const PROCESSING_BUFFER_SIZE = Math.pow(2, 12);
31
42
 
32
43
  export interface Config {
33
44
  audio: {
@@ -45,6 +56,17 @@ export interface Config {
45
56
  x?: number | PositionAliasName;
46
57
  y?: number | PositionAliasName;
47
58
  rotation?: RotationAliasName;
59
+ effect?: SpectrumEffect;
60
+ color?: Color | string;
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;
48
70
  color?: Color | string;
49
71
  opacity?: string;
50
72
  }
@@ -71,8 +93,67 @@ export type RotationAliasName = typeof rotationAliasValues[number];
71
93
  const sleep = (timeout: number) =>
72
94
  new Promise(resolve => setTimeout(resolve, timeout));
73
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
+
74
150
  export const renderAudioVisualizer = (config: Config, onProgress?: (progress: number) => any, shouldStop?: () => boolean) =>
75
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
+
76
157
  const audioFilePath = getAudioFilePath(config);
77
158
  const backgroundImagePath = getBackgroundImagePath(config);
78
159
  const outVideoPath = getOutVideoPath(config);
@@ -86,30 +167,18 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
86
167
  throw new Error('ffmpeg didn\'t show audio sample rate');
87
168
  }
88
169
 
89
- const spectrumBusesCount = getSpectrumBusesCount();
90
170
  const spectrumBusMargin = getSpectrumBusMargin();
91
171
  const FPS = getFPS(config);
92
- const spectrumWidth =
93
- getSpectrumWidthAbsolute(config, backgroundImage.width);
94
- const spectrumHeight =
95
- getSpectrumHeightAbsolute(config, backgroundImage.height);
96
- const spectrumX =
97
- getSpectrumXAbsolute(config, spectrumWidth, backgroundImage.width);
98
- const spectrumY =
99
- getSpectrumYAbsolute(config, spectrumHeight, backgroundImage.height);
100
- const spectrumRotation =
101
- getSpectrumRotation(config);
102
- const spectrumColor =
103
- getSpectrumColor(config) ||
104
- invertColor(getImageColor(backgroundImage));
105
- const spectrumOpacity = getSpectrumOpacityParsed(config);
106
172
  const ffmpeg_cfr = getFfmpeg_cfr(config);
107
173
  const ffmpeg_preset = getFfmpeg_preset(config);
108
174
  const frame_processing_delay = getFrame_processing_delay(config);
109
175
 
176
+ const createVisualizerFrame = createVisualizerFrameGenerator(config, backgroundImage, spectrumBusMargin);
177
+
110
178
  const audioDuration = audioBuffer.length / sampleRate;
111
179
  const framesCount = Math.trunc(audioDuration * FPS);
112
180
  const audioDataStep = Math.trunc(audioBuffer.length / framesCount);
181
+ const processingBuffer = new Float32Array(PROCESSING_BUFFER_SIZE).fill(0);
113
182
 
114
183
  const ffmpegVideoWriter = spawnFfmpegVideoWriter({
115
184
  audioFilename: audioFilePath,
@@ -123,21 +192,21 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
123
192
 
124
193
  const bpmEncoder = createBpmEncoder({ width: backgroundImage.width, height: backgroundImage.height });
125
194
  const backgroundImageBuffer = bpmEncoder(backgroundImage.data);
126
- const processSpectrum = createSpectrumsProcessor(spectrumBusesCount);
195
+ const skipFramesCount = FPS < 45 ? 1 : 2;
196
+ const processSpectrum = createSpectrumsProcessor(sampleRate, skipFramesCount);
197
+
127
198
  for (let i = 0; i < framesCount; i++) {
128
- const audioDataParser = () =>
129
- PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
130
- const spectrum = processSpectrum(i, audioDataParser);
131
- const frameImage = createVisualizerFrame({
199
+ const currentFrameData = PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
200
+ processingBuffer.copyWithin(0, currentFrameData.length);
201
+ processingBuffer.set(currentFrameData, PROCESSING_BUFFER_SIZE - currentFrameData.length);
202
+
203
+ const audioDataParser = () => Array.from(processingBuffer);
204
+ const spectrum = processSpectrum(audioDataParser);
205
+ const commonVisualizerFrameProps: CommonVisualizerFrameProps = {
132
206
  backgroundImageBuffer,
133
207
  spectrum,
134
- size: { width: spectrumWidth, height: spectrumHeight },
135
- position: { x: spectrumX, y: spectrumY },
136
- rotation: spectrumRotation,
137
- margin: spectrumBusMargin,
138
- color: spectrumColor,
139
- opacity: spectrumOpacity,
140
- });
208
+ };
209
+ const frameImage = createVisualizerFrame(commonVisualizerFrameProps);
141
210
  const isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
142
211
  if (!isFrameProcessed) {
143
212
  await waitDrain(ffmpegVideoWriter.stdin);
@@ -8,7 +8,6 @@ import {
8
8
  skipEvery,
9
9
  getPeaks,
10
10
  correctPeaks,
11
- smoothValues,
12
11
  createSpectrumsProcessor,
13
12
  } from '../audio';
14
13
  import { createSandbox } from 'sinon';
@@ -85,21 +84,13 @@ describe('audio', function () {
85
84
  expect(result).deep.equal(expected);
86
85
  });
87
86
 
88
- it('smoothValues', () => {
89
- const spectrums = [1, 2, 3, 4];
90
- const prevSpectrums = [2, 4, 6, 3];
91
- const result = smoothValues(spectrums, prevSpectrums);
92
- const expected = [1.5, 3, 4.5, 3.5];
93
- expect(result).deep.equal(expected);
94
- });
95
87
 
96
88
  it('createSpectrumsProcessor', () => {
97
- const busesCount = 2;
98
- const spectrumsProcessor = createSpectrumsProcessor(busesCount);
89
+ const spectrumsProcessor = createSpectrumsProcessor(44100);
99
90
 
100
- const frame0 = spectrumsProcessor(0, () => ([1, 2, 3, 4]));
101
- expect(frame0).to.have.length(2);
102
- const frame1 = spectrumsProcessor(1, () => ([2, 2, 6, 2]));
103
- expect(frame1).to.have.length(2);
91
+ const frame0 = spectrumsProcessor(() => ([1, 2, 3, 4]));
92
+ expect(frame0).to.have.length(24);
93
+ const frame1 = spectrumsProcessor(() => ([2, 2, 6, 2]));
94
+ expect(frame1).to.have.length(24);
104
95
  });
105
96
  });
@@ -79,9 +79,9 @@ describe('config', function() {
79
79
  );
80
80
 
81
81
  const result2 = getSpectrumWidthAbsolute({
82
- outVideo: { spectrum: { width: 420 }}
82
+ outVideo: { spectrum: { width: 600 }}
83
83
  } as Config, backgroundImageWidth);
84
- expect(result2).equal(420);
84
+ expect(result2).equal(600);
85
85
 
86
86
  const result3 = getSpectrumWidthAbsolute({
87
87
  outVideo: { spectrum: { width: '69%' }}
@@ -117,13 +117,11 @@ describe('config', function() {
117
117
  });
118
118
 
119
119
  it('getSpectrumHeightAbsolute', function() {
120
- const backgroundImageHeight = 768;
120
+ const backgroundImageHeight = 720;
121
121
  const result1 = getSpectrumHeightAbsolute({
122
122
  outVideo: {}
123
123
  } as Config, backgroundImageHeight);
124
- expect(result1).equal(
125
- backgroundImageHeight * (parseInt(defaults.spectrumHeight) / 100)
126
- );
124
+ expect(result1).equal(160);
127
125
 
128
126
  const result2 = getSpectrumHeightAbsolute({
129
127
  outVideo: { spectrum: { height: 420 }}
@@ -212,7 +210,7 @@ describe('config', function() {
212
210
 
213
211
  it('getSpectrumYAbsolute', function() {
214
212
  const spectrumHeight = 125;
215
- const backgroundImageHeight = 768;
213
+ const backgroundImageHeight = 721;
216
214
  const result1 = getSpectrumYAbsolute(
217
215
  {
218
216
  outVideo: {},
@@ -221,7 +219,7 @@ describe('config', function() {
221
219
  backgroundImageHeight
222
220
  );
223
221
  expect(result1).equal(
224
- 0
222
+ 298
225
223
  );
226
224
 
227
225
  const result2 = getSpectrumYAbsolute(