nodejs-audio-visualizer 2.1.4 → 3.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/dist/index.js CHANGED
@@ -58,12 +58,13 @@ exports.PCM_FORMAT = {
58
58
  parseFunction: audio_1.bufferToUInt8
59
59
  };
60
60
  var FFMPEG_FORMAT = "" + exports.PCM_FORMAT.sign + exports.PCM_FORMAT.bit;
61
+ var PROCESSING_BUFFER_SIZE = Math.pow(2, 12);
61
62
  var sleep = function (timeout) {
62
63
  return new Promise(function (resolve) { return setTimeout(resolve, timeout); });
63
64
  };
64
65
  exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
65
66
  return new Promise(function (resolve) { return __awaiter(void 0, void 0, void 0, function () {
66
- var audioFilePath, backgroundImagePath, outVideoPath, backgroundImageBmpBuffer, backgroundImage, audioReader, audioBuffer, sampleRate, spectrumBusesCount, spectrumBusMargin, FPS, spectrumWidth, spectrumHeight, spectrumX, spectrumY, spectrumRotation, spectrumColor, spectrumOpacity, ffmpeg_cfr, ffmpeg_preset, frame_processing_delay, audioDuration, framesCount, audioDataStep, ffmpegVideoWriter, bpmEncoder, backgroundImageBuffer, processSpectrum, _loop_1, i, state_1;
67
+ var audioFilePath, backgroundImagePath, outVideoPath, backgroundImageBmpBuffer, backgroundImage, audioReader, audioBuffer, sampleRate, spectrumBusMargin, FPS, spectrumWidth, spectrumHeight, spectrumX, spectrumY, spectrumRotation, spectrumColor, spectrumEffect, spectrumOpacity, ffmpeg_cfr, ffmpeg_preset, frame_processing_delay, audioDuration, framesCount, audioDataStep, processingBuffer, ffmpegVideoWriter, bpmEncoder, backgroundImageBuffer, skipFramesCount, processSpectrum, createVisualizerFrame, i, currentFrameData, audioDataParser, spectrum, frameImage, isFrameProcessed;
67
68
  return __generator(this, function (_a) {
68
69
  switch (_a.label) {
69
70
  case 0:
@@ -82,7 +83,6 @@ exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
82
83
  if (!sampleRate) {
83
84
  throw new Error('ffmpeg didn\'t show audio sample rate');
84
85
  }
85
- spectrumBusesCount = config_1.getSpectrumBusesCount();
86
86
  spectrumBusMargin = config_1.getSpectrumBusMargin();
87
87
  FPS = config_1.getFPS(config);
88
88
  spectrumWidth = config_1.getSpectrumWidthAbsolute(config, backgroundImage.width);
@@ -92,6 +92,7 @@ exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
92
92
  spectrumRotation = config_1.getSpectrumRotation(config);
93
93
  spectrumColor = config_1.getSpectrumColor(config) ||
94
94
  image_1.invertColor(image_1.getImageColor(backgroundImage));
95
+ spectrumEffect = config_1.getSpectrumEffect(config);
95
96
  spectrumOpacity = config_1.getSpectrumOpacityParsed(config);
96
97
  ffmpeg_cfr = config_1.getFfmpeg_cfr(config);
97
98
  ffmpeg_preset = config_1.getFfmpeg_preset(config);
@@ -99,63 +100,53 @@ exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
99
100
  audioDuration = audioBuffer.length / sampleRate;
100
101
  framesCount = Math.trunc(audioDuration * FPS);
101
102
  audioDataStep = Math.trunc(audioBuffer.length / framesCount);
103
+ processingBuffer = new Float32Array(PROCESSING_BUFFER_SIZE).fill(0);
102
104
  ffmpegVideoWriter = video_1.spawnFfmpegVideoWriter(__assign(__assign(__assign({ audioFilename: audioFilePath, videoFileName: outVideoPath, fps: FPS }, (!!onProgress && { onStderr: video_1.getProgress(video_1.calculateProgress(framesCount + 1, onProgress)) })), (ffmpeg_cfr && { crf: ffmpeg_cfr })), (ffmpeg_preset && { preset: ffmpeg_preset })));
103
105
  ffmpegVideoWriter.on('exit', function (code) { return resolve(code); });
104
106
  bpmEncoder = bpmEncoder_1.createBpmEncoder({ width: backgroundImage.width, height: backgroundImage.height });
105
107
  backgroundImageBuffer = bpmEncoder(backgroundImage.data);
106
- processSpectrum = audio_1.createSpectrumsProcessor(spectrumBusesCount);
107
- _loop_1 = function (i) {
108
- var audioDataParser, spectrum, frameImage, isFrameProcessed;
109
- return __generator(this, function (_a) {
110
- switch (_a.label) {
111
- case 0:
112
- audioDataParser = function () {
113
- return exports.PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
114
- };
115
- spectrum = processSpectrum(i, audioDataParser);
116
- frameImage = image_1.createVisualizerFrame({
117
- backgroundImageBuffer: backgroundImageBuffer,
118
- spectrum: spectrum,
119
- size: { width: spectrumWidth, height: spectrumHeight },
120
- position: { x: spectrumX, y: spectrumY },
121
- rotation: spectrumRotation,
122
- margin: spectrumBusMargin,
123
- color: spectrumColor,
124
- opacity: spectrumOpacity,
125
- });
126
- isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
127
- if (!!isFrameProcessed) return [3 /*break*/, 2];
128
- return [4 /*yield*/, video_1.waitDrain(ffmpegVideoWriter.stdin)];
129
- case 1:
130
- _a.sent();
131
- _a.label = 2;
132
- case 2:
133
- if (shouldStop && shouldStop()) {
134
- return [2 /*return*/, "break"];
135
- }
136
- if (!frame_processing_delay) return [3 /*break*/, 4];
137
- return [4 /*yield*/, sleep(frame_processing_delay)];
138
- case 3:
139
- _a.sent();
140
- _a.label = 4;
141
- case 4: return [2 /*return*/];
142
- }
143
- });
144
- };
108
+ skipFramesCount = FPS < 45 ? 1 : 2;
109
+ processSpectrum = audio_1.createSpectrumsProcessor(sampleRate, skipFramesCount);
110
+ createVisualizerFrame = image_1.createVisualizerFrameGenerator();
145
111
  i = 0;
146
112
  _a.label = 3;
147
113
  case 3:
148
- if (!(i < framesCount)) return [3 /*break*/, 6];
149
- return [5 /*yield**/, _loop_1(i)];
114
+ if (!(i < framesCount)) return [3 /*break*/, 8];
115
+ currentFrameData = exports.PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
116
+ processingBuffer.copyWithin(0, currentFrameData.length);
117
+ processingBuffer.set(currentFrameData, PROCESSING_BUFFER_SIZE - currentFrameData.length);
118
+ audioDataParser = function () { return Array.from(processingBuffer); };
119
+ spectrum = processSpectrum(audioDataParser);
120
+ frameImage = createVisualizerFrame({
121
+ backgroundImageBuffer: backgroundImageBuffer,
122
+ spectrum: spectrum,
123
+ size: { width: spectrumWidth, height: spectrumHeight },
124
+ position: { x: spectrumX, y: spectrumY },
125
+ rotation: spectrumRotation,
126
+ margin: spectrumBusMargin,
127
+ color: spectrumColor,
128
+ opacity: spectrumOpacity,
129
+ spectrumEffect: spectrumEffect,
130
+ });
131
+ isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
132
+ if (!!isFrameProcessed) return [3 /*break*/, 5];
133
+ return [4 /*yield*/, video_1.waitDrain(ffmpegVideoWriter.stdin)];
150
134
  case 4:
151
- state_1 = _a.sent();
152
- if (state_1 === "break")
153
- return [3 /*break*/, 6];
135
+ _a.sent();
154
136
  _a.label = 5;
155
137
  case 5:
138
+ if (shouldStop && shouldStop()) {
139
+ return [3 /*break*/, 8];
140
+ }
141
+ if (!frame_processing_delay) return [3 /*break*/, 7];
142
+ return [4 /*yield*/, sleep(frame_processing_delay)];
143
+ case 6:
144
+ _a.sent();
145
+ _a.label = 7;
146
+ case 7:
156
147
  i++;
157
148
  return [3 /*break*/, 3];
158
- case 6:
149
+ case 8:
159
150
  ffmpegVideoWriter.stdin.end();
160
151
  return [2 /*return*/];
161
152
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-audio-visualizer",
3
- "version": "2.1.4",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "src/"
10
10
  ],
11
11
  "scripts": {
12
- "test": "mocha -r ts-node/register src/**/*.test.ts",
12
+ "test": "ts-mocha -p tsconfig.json src/**/*.test.ts",
13
13
  "dev": "ts-node-dev --respawn --transpileOnly ./example/example.ts",
14
14
  "prebuild": "rimraf dist",
15
15
  "build": "tsc -m CommonJS",
@@ -37,6 +37,7 @@
37
37
  "mocha": "^6.2.3",
38
38
  "rimraf": "^3.0.2",
39
39
  "sinon": "^7.5.0",
40
+ "ts-mocha": "^11.1.0",
40
41
  "ts-node": "^8.5.0",
41
42
  "ts-node-dev": "^1.0.0-pre.43",
42
43
  "typescript": "^3.7.2"
@@ -54,6 +55,7 @@
54
55
  "@types/sinon": "^7.5.0",
55
56
  "bmp-js": "^0.1.0",
56
57
  "ffmpeg-static": "^5.2.0",
58
+ "fft-js": "^0.0.12",
57
59
  "jimp": "^0.9.3"
58
60
  }
59
61
  }
package/src/audio.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'child_process';
2
2
  import ffmpegPath from 'ffmpeg-static';
3
- import { getSpectrum } from './dsp';
3
+ import { FREQUENCY_BANDS, getSpectrum } from './dsp';
4
4
 
5
5
  export const skipEvery = <T>(skipIndex: number) => (element: T, index: number) =>
6
6
  index % skipIndex === 0;
@@ -34,44 +34,46 @@ export const correctPeaks = (spectrums: number[], peaks: number[]) => {
34
34
  return resultSpectrum;
35
35
  };
36
36
 
37
- export const smoothValues = (spectrums: number[], prevSpectrums?: number[]) => {
38
- if (!prevSpectrums) {
39
- return spectrums;
40
- }
41
- const resultSpectrum: number[] = [];
42
- for(let i = 0; i < spectrums.length; i++) {
43
- const currValue = spectrums[i];
44
- const currPrevValue = prevSpectrums[i] || 0;
45
- const avgValue = (currValue + currPrevValue) / 2;
46
- resultSpectrum.push(avgValue);
47
- }
48
- return resultSpectrum;
37
+ const oldMax = 1;
38
+ const newMin = 0.6;
39
+ const getInterpolationFactor = (skippedFramesCount: number, skipFramesCount: number) => {
40
+ const interpolationFactor = 1 - (skippedFramesCount / skipFramesCount);
41
+ const interpolationFactorInRange = (interpolationFactor * (oldMax - newMin)) + newMin;
42
+ return interpolationFactorInRange;
49
43
  };
50
44
 
51
- export const createSpectrumsProcessor = (busesCount: number) => {
45
+ export const createSpectrumsProcessor = (sampleRate: number, skipFramesCount: number) => {
52
46
  let prevAudioDataNormalized: number[] = [];
53
47
  let prevPeaks: number[] = [];
54
- let prevSpectrums: number[] = [];
55
- const skipFrameIndex = 2;
56
-
57
- return (frameIndex: number, parseAudioData: () => number[]) => {
58
- const isFrameSkiped =
59
- frameIndex && (frameIndex % skipFrameIndex === 0);
60
- const audioDataNomrmalized = isFrameSkiped ?
61
- prevAudioDataNormalized :
62
- normalizeAudioData(parseAudioData());
63
- prevAudioDataNormalized = audioDataNomrmalized;
48
+ let skippedFramesCount = 0;
49
+ let targetSpectrum: number[] = [];
50
+ let previousSpectrum: number[] = [];
64
51
 
65
- const spectrum = getSpectrum(audioDataNomrmalized);
66
- const skipIndex = Math.trunc(spectrum.length / busesCount);
67
- const spectrumReduced = spectrum.filter(skipEvery(skipIndex));
68
- const peaks = getPeaks(spectrumReduced, prevPeaks);
69
- const correctedSpectrum = correctPeaks(spectrumReduced, peaks);
70
- const smoothSpectrum = smoothValues(correctedSpectrum, prevSpectrums);
71
- prevSpectrums = smoothSpectrum;
72
- prevPeaks = peaks;
52
+ return (parseAudioData: () => number[]) => {
53
+ if (skippedFramesCount === 0) {
54
+ skippedFramesCount = skipFramesCount;
55
+ const audioDataNomrmalized = normalizeAudioData(parseAudioData());
56
+ prevAudioDataNormalized = audioDataNomrmalized;
73
57
 
74
- return smoothSpectrum;
58
+ const spectrum = getSpectrum(audioDataNomrmalized, sampleRate);
59
+ const peaks = getPeaks(spectrum, prevPeaks);
60
+ const correctedSpectrum =
61
+ prevPeaks.length === 0 ?
62
+ Array(FREQUENCY_BANDS.length).fill(0) :
63
+ correctPeaks(spectrum, peaks);
64
+
65
+ prevPeaks = peaks;
66
+ previousSpectrum = [...targetSpectrum];
67
+ targetSpectrum = correctedSpectrum;
68
+ } else {
69
+ skippedFramesCount--;
70
+ }
71
+ const interpolationFactor = getInterpolationFactor(skippedFramesCount, skipFramesCount);
72
+ const interpolatedSpectrum = targetSpectrum.map((target, i) => {
73
+ const prev = previousSpectrum[i] || target;
74
+ return prev + (target - prev) * interpolationFactor;
75
+ });
76
+ return interpolatedSpectrum;
75
77
  };
76
78
  };
77
79
 
package/src/config.ts CHANGED
@@ -1,20 +1,22 @@
1
1
  import path from 'path';
2
2
  import { Config, SpectrumSizeValue, PositionAliasName, RotationAliasName } from './index';
3
+ import { FREQUENCY_BANDS } from './dsp';
3
4
 
4
5
  export const defaults = {
5
- fps: 60,
6
- spectrumWidth: '40%',
7
- spectrumHeight: '10%',
6
+ fps: 30,
7
+ spectrumWidth: '33%',
8
+ spectrumHeight: 160,
8
9
  spectrumX: 'center',
9
- spectrumY: 'top',
10
- spectrumRotation: 'down',
11
- spectrumBusesCount: 64,
12
- spectrumBusMargin: 4,
13
- spectrumOpacity: '100%',
10
+ spectrumY: 'middle',
11
+ spectrumRotation: 'mirror',
12
+ spectrumBusMargin: 12,
13
+ spectrumOpacity: '80%',
14
14
  };
15
15
 
16
16
  type RelativePositionValue = 0 | 0.5 | 1;
17
17
 
18
+ export type SpectrumEffect = 'volume' | 'smooth';
19
+
18
20
  interface PositionAlias {
19
21
  name: PositionAliasName;
20
22
  value: RelativePositionValue;
@@ -29,7 +31,7 @@ const postitionAliases: PositionAlias[] = [
29
31
  { name: 'bottom', value: 1 },
30
32
  ];
31
33
 
32
- export const rotationAliasValues = ['up', 'down'] as const;
34
+ export const rotationAliasValues = ['up', 'down', 'mirror'] as const;
33
35
 
34
36
  export const checkIsPercentValue = (value: string) =>
35
37
  RegExp(/\d+%/).test(value)
@@ -46,14 +48,15 @@ export const getOutVideoPath = (config: Config) =>
46
48
  export const getFPS = (config: Config) =>
47
49
  config.outVideo.fps || defaults.fps;
48
50
 
49
- export const getSpectrumBusesCount = () => defaults.spectrumBusesCount;
50
-
51
51
  export const getSpectrumBusMargin = () => defaults.spectrumBusMargin;
52
52
 
53
53
  const getSpectrumWidth = (config: Config) =>
54
54
  (config.outVideo.spectrum && config.outVideo.spectrum.width) ||
55
55
  defaults.spectrumWidth;
56
56
 
57
+ export const getSpectrumEffect = (config: Config) =>
58
+ config.outVideo.spectrum?.effect;
59
+
57
60
  const getSpectrumSizeAbsolute = (
58
61
  spectrumSize: SpectrumSizeValue,
59
62
  backgroundImageSize: number,
@@ -81,7 +84,7 @@ export const getSpectrumWidthAbsolute = (
81
84
  const spectrumWidth = getSpectrumWidth(config);
82
85
  const spectrumWidthAbsolute = getSpectrumSizeAbsolute(spectrumWidth, backgroundImageWidth, 'width');
83
86
  const spectrumWidthWithoutMargin =
84
- Math.trunc(spectrumWidthAbsolute / defaults.spectrumBusesCount) - defaults.spectrumBusMargin / 2;
87
+ Math.trunc(spectrumWidthAbsolute / FREQUENCY_BANDS.length) - defaults.spectrumBusMargin / 2;
85
88
  if (spectrumWidthWithoutMargin <= 0) {
86
89
  throw new Error(`Spectrum width '${spectrumWidth}' is too small.`);
87
90
  }
package/src/dsp.ts CHANGED
@@ -1,4 +1,62 @@
1
- import { createSpectrumAnalyzer } from './spectrum';
1
+ import { fft, util } from 'fft-js';
2
+
3
+ export interface SpectrumPoint {
4
+ frequency: number;
5
+ magnitude: number;
6
+ }
7
+
8
+ export interface FrequencyBus {
9
+ startFreq: number;
10
+ endFreq: number;
11
+ magnitude: number;
12
+ }
13
+
14
+ // Common frequency bands (in Hz)
15
+ export const FREQUENCY_BANDS = [
16
+ { name: 'Sub Bass 1', start: 20, end: 30 },
17
+ { name: 'Sub Bass 2', start: 30, end: 40 },
18
+ { name: 'Sub Bass 3', start: 50, end: 60 },
19
+ { name: 'Sub Bass 4', start: 20, end: 60 },
20
+ { name: 'Bass 1', start: 60, end: 107.5 },
21
+ { name: 'Bass 2', start: 107.5, end: 155 },
22
+ { name: 'Bass 3', start: 155, end: 202.5 },
23
+ { name: 'Bass 4', start: 202.5, end: 250 },
24
+ { name: 'Low Mids 1', start: 250, end: 312.5 },
25
+ { name: 'Low Mids 2', start: 312.5, end: 375 },
26
+ { name: 'Low Mids 3', start: 375, end: 437.5 },
27
+ { name: 'Low Mids 4', start: 437.5, end: 500 },
28
+ { name: 'Mids 1', start: 500, end: 875 },
29
+ { name: 'Mids 2', start: 875, end: 1250 },
30
+ { name: 'Mids 3', start: 1250, end: 1625 },
31
+ { name: 'Mids 4', start: 1625, end: 2000 },
32
+ { name: 'High Mids 1', start: 2000, end: 2500 },
33
+ { name: 'High Mids 2', start: 2500, end: 3000 },
34
+ { name: 'High Mids 3', start: 3000, end: 3500 },
35
+ { name: 'High Mids 4', start: 3500, end: 4000 },
36
+ { name: 'Presence 1', start: 4000, end: 5000 },
37
+ { name: 'Presence 2', start: 5000, end: 6000 },
38
+ { name: 'Brilliance 1', start: 6000, end: 13000 },
39
+ { name: 'Brilliance 2', start: 13000, end: 20000 },
40
+ ];
41
+
42
+ const combineToBuses = (spectrum: SpectrumPoint[]): number[] => {
43
+ return FREQUENCY_BANDS.map(band => {
44
+ const pointsInBand = spectrum.filter(point =>
45
+ point.frequency >= band.start && point.frequency < band.end
46
+ );
47
+
48
+ // If no points found in this band, return zero magnitude
49
+ if (pointsInBand.length === 0) {
50
+ return 0;
51
+ }
52
+
53
+ // Average the magnitudes in this frequency band
54
+ const avgMagnitude = pointsInBand.reduce((sum, point) =>
55
+ sum + point.magnitude, 0) / pointsInBand.length;
56
+
57
+ return avgMagnitude;
58
+ });
59
+ };
2
60
 
3
61
  export const brutForceFFTSignalLength = (PCMDataLength: number) => {
4
62
  let exponent = 1;
@@ -11,16 +69,27 @@ export const brutForceFFTSignalLength = (PCMDataLength: number) => {
11
69
  }
12
70
  };
13
71
 
14
- export const getSpectrum = (PCMData: number[]) => {
72
+ export const getSpectrum = (PCMData: number[], sampleRate: number): number[] => {
15
73
  const signalLength = brutForceFFTSignalLength(PCMData.length);
16
74
  const signalStartIndex = Math.trunc(PCMData.length / 2 - signalLength / 2);
17
75
  const signal = PCMData.slice(signalStartIndex, signalStartIndex + signalLength);
18
76
 
19
- const spectrumAnalyzer = createSpectrumAnalyzer(signalLength);
20
- const spectrum = spectrumAnalyzer(signal);
77
+ // Use fft-js to compute the FFT
78
+ const phasors = fft(signal);
79
+ const frequencies = util.fftFreq(phasors, sampleRate);
80
+ const magnitudes = util.fftMag(phasors);
81
+
82
+ // Create combined spectrum array with both frequency and magnitude
83
+ let spectrum: SpectrumPoint[] = frequencies.map((freq: number, i: number) => ({
84
+ frequency: freq,
85
+ magnitude: magnitudes[i]
86
+ }));
87
+
88
+ // Apply frequency-dependent scaling
21
89
  for (let i = 0; i < spectrum.length; i++) {
22
- // attenuates low freqs and boosts highs
23
- spectrum[i] *= -1 * Math.log((signalLength / 2 - i) * (0.5 / signalLength / 2)) * signalLength;
90
+ spectrum[i].magnitude *= -1 * Math.log((signalLength / 2 - i) * (0.5 / signalLength / 2)) * signalLength;
24
91
  }
25
- return spectrum;
92
+
93
+ // Combine frequencies into buses
94
+ return combineToBuses(spectrum);
26
95
  }
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;
@@ -111,6 +112,27 @@ interface DrawSpectrumProps {
111
112
  opacity: number;
112
113
  }
113
114
 
115
+ const getRectY = (rotation: RotationAliasName, top: number, height: number, rectHeight: number) => {
116
+ switch (rotation) {
117
+ case 'up':
118
+ return top + height - rectHeight;
119
+ case 'mirror':
120
+ return top + Math.trunc((height - rectHeight) / 2);
121
+ default:
122
+ return top;
123
+ }
124
+ };
125
+
126
+ const minRectHeight = 2;
127
+
128
+ const getRectHeight = (height: number, spectrumValue: number) => {
129
+ const rectHeight = Math.trunc(height * spectrumValue);
130
+ if (rectHeight < minRectHeight) {
131
+ return minRectHeight;
132
+ }
133
+ return rectHeight;
134
+ };
135
+
114
136
  const drawSpectrum = ({
115
137
  imageDstBuffer,
116
138
  spectrum,
@@ -133,8 +155,8 @@ const drawSpectrum = ({
133
155
  }
134
156
 
135
157
  const rectX = left + busWidth * spectrumX;
136
- const rectHeight = Math.trunc(height * spectrumValue);
137
- const rectY = (rotation === 'up') ? top + height - rectHeight : top;
158
+ const rectHeight = getRectHeight(height, spectrumValue);
159
+ const rectY = getRectY(rotation, top, height, rectHeight);
138
160
  drawRect({
139
161
  imageDstBuffer,
140
162
  position: { x: rectX + margin, y: rectY },
@@ -154,33 +176,67 @@ interface CreateVisualizerFrameProps {
154
176
  margin: number;
155
177
  color: Color | string;
156
178
  opacity: number;
179
+ spectrumEffect?: SpectrumEffect;
157
180
  }
158
181
 
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);
182
+ export const createVisualizerFrameGenerator = () => {
183
+ let prevSpectrum: number[] | null = null;
171
184
 
172
- const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
173
- drawSpectrum({
174
- imageDstBuffer,
185
+ return ({
186
+ backgroundImageBuffer,
175
187
  spectrum,
176
188
  size,
177
189
  position,
178
190
  rotation,
179
191
  margin,
180
- color: rgbSpectrumColor,
192
+ color,
181
193
  opacity,
182
- });
183
- return imageDstBuffer;
194
+ spectrumEffect,
195
+ }: CreateVisualizerFrameProps) => {
196
+ const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
197
+ imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
198
+
199
+
200
+ const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
201
+ if (spectrumEffect === 'volume') {
202
+ drawSpectrum({
203
+ imageDstBuffer,
204
+ spectrum,
205
+ size,
206
+ position: { x: position.x + 2, y: position.y + 2 },
207
+ rotation,
208
+ margin,
209
+ color: rgbSpectrumColor,
210
+ opacity: opacity * 0.5,
211
+ });
212
+ }
213
+ if (spectrumEffect === 'smooth' && prevSpectrum) {
214
+ drawSpectrum({
215
+ imageDstBuffer,
216
+ spectrum: prevSpectrum,
217
+ size,
218
+ position,
219
+ rotation,
220
+ margin,
221
+ color: rgbSpectrumColor,
222
+ opacity: opacity * 0.3,
223
+ });
224
+ }
225
+ drawSpectrum({
226
+ imageDstBuffer,
227
+ spectrum,
228
+ size,
229
+ position,
230
+ rotation,
231
+ margin,
232
+ color: rgbSpectrumColor,
233
+ opacity,
234
+ });
235
+
236
+ prevSpectrum = spectrum;
237
+
238
+ return imageDstBuffer;
239
+ };
184
240
  };
185
241
 
186
242
  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,10 +14,12 @@ import {
15
14
  getFfmpeg_preset,
16
15
  getFrame_processing_delay,
17
16
  rotationAliasValues,
18
- getSpectrumRotation
17
+ getSpectrumRotation,
18
+ getSpectrumEffect,
19
+ SpectrumEffect,
19
20
  } from './config';
20
21
  import { createAudioBuffer, bufferToUInt8, createSpectrumsProcessor } from './audio';
21
- import { parseImage, createVisualizerFrame, getImageColor, invertColor, Color, convertToBmp } from './image';
22
+ import { parseImage, getImageColor, invertColor, Color, convertToBmp, createVisualizerFrameGenerator } from './image';
22
23
  import { spawnFfmpegVideoWriter, getProgress, calculateProgress, waitDrain } from './video';
23
24
  import { createBpmEncoder } from './bpmEncoder';
24
25
 
@@ -28,6 +29,7 @@ export const PCM_FORMAT = {
28
29
  parseFunction: bufferToUInt8
29
30
  };
30
31
  const FFMPEG_FORMAT = `${PCM_FORMAT.sign}${PCM_FORMAT.bit}`;
32
+ const PROCESSING_BUFFER_SIZE = Math.pow(2, 12);
31
33
 
32
34
  export interface Config {
33
35
  audio: {
@@ -45,6 +47,7 @@ export interface Config {
45
47
  x?: number | PositionAliasName;
46
48
  y?: number | PositionAliasName;
47
49
  rotation?: RotationAliasName;
50
+ effect?: SpectrumEffect;
48
51
  color?: Color | string;
49
52
  opacity?: string;
50
53
  }
@@ -86,7 +89,6 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
86
89
  throw new Error('ffmpeg didn\'t show audio sample rate');
87
90
  }
88
91
 
89
- const spectrumBusesCount = getSpectrumBusesCount();
90
92
  const spectrumBusMargin = getSpectrumBusMargin();
91
93
  const FPS = getFPS(config);
92
94
  const spectrumWidth =
@@ -102,6 +104,7 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
102
104
  const spectrumColor =
103
105
  getSpectrumColor(config) ||
104
106
  invertColor(getImageColor(backgroundImage));
107
+ const spectrumEffect = getSpectrumEffect(config);
105
108
  const spectrumOpacity = getSpectrumOpacityParsed(config);
106
109
  const ffmpeg_cfr = getFfmpeg_cfr(config);
107
110
  const ffmpeg_preset = getFfmpeg_preset(config);
@@ -110,6 +113,7 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
110
113
  const audioDuration = audioBuffer.length / sampleRate;
111
114
  const framesCount = Math.trunc(audioDuration * FPS);
112
115
  const audioDataStep = Math.trunc(audioBuffer.length / framesCount);
116
+ const processingBuffer = new Float32Array(PROCESSING_BUFFER_SIZE).fill(0);
113
117
 
114
118
  const ffmpegVideoWriter = spawnFfmpegVideoWriter({
115
119
  audioFilename: audioFilePath,
@@ -123,11 +127,16 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
123
127
 
124
128
  const bpmEncoder = createBpmEncoder({ width: backgroundImage.width, height: backgroundImage.height });
125
129
  const backgroundImageBuffer = bpmEncoder(backgroundImage.data);
126
- const processSpectrum = createSpectrumsProcessor(spectrumBusesCount);
130
+ const skipFramesCount = FPS < 45 ? 1 : 2;
131
+ const processSpectrum = createSpectrumsProcessor(sampleRate, skipFramesCount);
132
+ const createVisualizerFrame = createVisualizerFrameGenerator();
127
133
  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);
134
+ const currentFrameData = PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
135
+ processingBuffer.copyWithin(0, currentFrameData.length);
136
+ processingBuffer.set(currentFrameData, PROCESSING_BUFFER_SIZE - currentFrameData.length);
137
+
138
+ const audioDataParser = () => Array.from(processingBuffer);
139
+ const spectrum = processSpectrum(audioDataParser);
131
140
  const frameImage = createVisualizerFrame({
132
141
  backgroundImageBuffer,
133
142
  spectrum,
@@ -137,6 +146,7 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
137
146
  margin: spectrumBusMargin,
138
147
  color: spectrumColor,
139
148
  opacity: spectrumOpacity,
149
+ spectrumEffect,
140
150
  });
141
151
  const isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
142
152
  if (!isFrameProcessed) {