nodejs-audio-visualizer 2.1.3 → 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/dist/video.js CHANGED
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  var child_process_1 = require("child_process");
4
- var ffmpeg_static_1 = require("ffmpeg-static");
7
+ var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
5
8
  exports.spawnFfmpegVideoWriter = function (config) {
9
+ if (!ffmpeg_static_1.default) {
10
+ throw new Error('ffmpeg path not found');
11
+ }
6
12
  var crf = config.crf || '23';
7
13
  var preset = config.preset || 'medium';
8
14
  var args = [
@@ -14,7 +20,7 @@ exports.spawnFfmpegVideoWriter = function (config) {
14
20
  '-r', "" + config.fps,
15
21
  '-i', '-'
16
22
  ];
17
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.path, args);
23
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, args);
18
24
  ffmpeg.stdin.pipe(process.stdout);
19
25
  if (config.onStderr) {
20
26
  ffmpeg.stderr.on('data', config.onStderr);
package/dist/video.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":";;AACA,+CAAsC;AACtC,+CAAmD;AAWtC,QAAA,sBAAsB,GAAG,UAAC,MAA+B;IACpE,IAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC/B,IAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC;IACzC,IAAM,IAAI,GAAG;QACX,IAAI;QACJ,IAAI,EAAE,MAAM,CAAC,aAAa;QAC1B,MAAM,EAAE,GAAG;QACX,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS;QACtD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa;QACxG,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK;QACrB,IAAI,EAAE,GAAG;KACV,CAAC;IACF,IAAM,MAAM,GAAG,qBAAK,CAAC,oBAAU,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,QAAQ,EAAE;QACnB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;KAC3C;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEW,QAAA,WAAW,GAAG,UAAC,UAAyC;IACnE,OAAA,UAAC,YAAoB;QACnB,IAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO;SACR;QACD,IAAM,YAAY,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE;YACvB,OAAO;SACR;QACD,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;AAVD,CAUC,CAAC;AAES,QAAA,iBAAiB,GAAG,UAAC,WAAmB,EAAE,QAAmC;IACxF,OAAA,UAAC,YAAoB;QACnB,OAAA,QAAQ,CACN,CAAC,CAAC,YAAY,GAAG,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC/C;IAFD,CAEC;AAHH,CAGG,CAAC;AAEO,QAAA,SAAS,GAAG,UAAC,MAAgB;IACxC,OAAA,IAAI,OAAO,CAAO,UAAA,OAAO,IAAI,OAAA,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAA7B,CAA6B,CAAC;AAA3D,CAA2D,CAAC"}
1
+ {"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":";;;;;AACA,+CAAsC;AACtC,gEAAuC;AAW1B,QAAA,sBAAsB,GAAG,UAAC,MAA+B;IACpE,IAAI,CAAC,uBAAU,EAAE;QACf,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;KAC1C;IACD,IAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC/B,IAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC;IACzC,IAAM,IAAI,GAAG;QACX,IAAI;QACJ,IAAI,EAAE,MAAM,CAAC,aAAa;QAC1B,MAAM,EAAE,GAAG;QACX,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS;QACtD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa;QACxG,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK;QACrB,IAAI,EAAE,GAAG;KACV,CAAC;IACF,IAAM,MAAM,GAAG,qBAAK,CAAC,uBAAU,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,QAAQ,EAAE;QACnB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;KAC3C;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEW,QAAA,WAAW,GAAG,UAAC,UAAyC;IACnE,OAAA,UAAC,YAAoB;QACnB,IAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO;SACR;QACD,IAAM,YAAY,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE;YACvB,OAAO;SACR;QACD,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;AAVD,CAUC,CAAC;AAES,QAAA,iBAAiB,GAAG,UAAC,WAAmB,EAAE,QAAmC;IACxF,OAAA,UAAC,YAAoB;QACnB,OAAA,QAAQ,CACN,CAAC,CAAC,YAAY,GAAG,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC/C;IAFD,CAEC;AAHH,CAGG,CAAC;AAEO,QAAA,SAAS,GAAG,UAAC,MAAgB;IACxC,OAAA,IAAI,OAAO,CAAO,UAAA,OAAO,IAAI,OAAA,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAA7B,CAA6B,CAAC;AAA3D,CAA2D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-audio-visualizer",
3
- "version": "2.1.3",
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"
@@ -50,10 +51,11 @@
50
51
  "@jimp/bmp": "^0.9.3",
51
52
  "@jimp/png": "^0.9.3",
52
53
  "@types/bmp-js": "^0.1.0",
53
- "@types/ffmpeg-static": "^2.0.0",
54
+ "@types/ffmpeg-static": "^3.0.3",
54
55
  "@types/sinon": "^7.5.0",
55
56
  "bmp-js": "^0.1.0",
56
- "ffmpeg-static": "^2.6.0",
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
- import ffmpeg from 'ffmpeg-static';
3
- import { getSpectrum } from './dsp';
2
+ import ffmpegPath from 'ffmpeg-static';
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;
48
+ let skippedFramesCount = 0;
49
+ let targetSpectrum: number[] = [];
50
+ let previousSpectrum: number[] = [];
56
51
 
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;
52
+ return (parseAudioData: () => number[]) => {
53
+ if (skippedFramesCount === 0) {
54
+ skippedFramesCount = skipFramesCount;
55
+ const audioDataNomrmalized = normalizeAudioData(parseAudioData());
56
+ prevAudioDataNormalized = audioDataNomrmalized;
64
57
 
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;
73
-
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
 
@@ -87,7 +89,10 @@ export const normalizeAudioData = (PCMData: number[]) =>
87
89
  PCMData.map(num => (num - 128) / 128);
88
90
 
89
91
  export const spawnFfmpegAudioReader = (filename: string, format: string) => {
90
- const ffmpegProcess = spawn(ffmpeg.path, ['-i', filename, '-f', format, '-ac', '1', '-']);
92
+ if (!ffmpegPath) {
93
+ throw new Error('ffmpeg path not found');
94
+ }
95
+ const ffmpegProcess = spawn(ffmpegPath, ['-i', filename, '-f', format, '-ac', '1', '-']);
91
96
  return ffmpegProcess;
92
97
  };
93
98
 
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) =>