nodejs-audio-visualizer 5.1.0 → 5.3.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.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +81 -80
  3. package/dist/audio.d.ts +13 -13
  4. package/dist/audio.js +128 -128
  5. package/dist/beats.d.ts +39 -39
  6. package/dist/beats.js +220 -220
  7. package/dist/bpmEncoder.d.ts +14 -14
  8. package/dist/bpmEncoder.js +97 -97
  9. package/dist/config.d.ts +78 -77
  10. package/dist/config.js +377 -369
  11. package/dist/config.js.map +1 -1
  12. package/dist/dsp.d.ts +16 -16
  13. package/dist/dsp.js +76 -76
  14. package/dist/highlight.d.ts +66 -34
  15. package/dist/highlight.js +471 -334
  16. package/dist/highlight.js.map +1 -1
  17. package/dist/image.d.ts +97 -92
  18. package/dist/image.js +545 -538
  19. package/dist/image.js.map +1 -1
  20. package/dist/index.d.ts +87 -79
  21. package/dist/index.js +598 -566
  22. package/dist/index.js.map +1 -1
  23. package/dist/subtitleConvert.d.ts +3 -3
  24. package/dist/subtitleConvert.js +61 -61
  25. package/dist/video.d.ts +150 -120
  26. package/dist/video.js +677 -582
  27. package/dist/video.js.map +1 -1
  28. package/dist/waitForEventLoop.d.ts +1 -1
  29. package/dist/waitForEventLoop.js +5 -5
  30. package/package.json +62 -62
  31. package/src/audio.ts +129 -129
  32. package/src/beats.ts +279 -279
  33. package/src/bpmEncoder.ts +97 -97
  34. package/src/config.ts +487 -476
  35. package/src/dsp.ts +95 -95
  36. package/src/highlight.ts +451 -291
  37. package/src/image.ts +707 -700
  38. package/src/index.ts +813 -769
  39. package/src/subtitleConvert.ts +63 -63
  40. package/src/tests/audio.test.ts +102 -102
  41. package/src/tests/beats.test.ts +134 -134
  42. package/src/tests/config.test.ts +534 -496
  43. package/src/tests/dsp.test.ts +24 -24
  44. package/src/tests/highlight.test.ts +261 -178
  45. package/src/tests/image.test.ts +319 -308
  46. package/src/tests/index.test.ts +180 -180
  47. package/src/tests/subtitleConvert.test.ts +23 -23
  48. package/src/tests/video.test.ts +535 -447
  49. package/src/typings.d.ts +28 -28
  50. package/src/video.ts +856 -722
  51. package/src/waitForEventLoop.ts +2 -2
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2019 VladislavPetyukevich
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2019 VladislavPetyukevich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,80 +1,81 @@
1
- # nodejs-audio-visualizer
2
- Audio visualizer generator for Node.js with simple configuration.
3
- Supports PNG, JPG for images, MP4/MOV/AVI/MKV for background videos, and MP3, WAV for audio.
4
-
5
- ## Example
6
- ```javascript
7
- const renderAudioVisualizer = require('nodejs-audio-visualizer').renderAudioVisualizer;
8
- const config = {
9
- image: { // Optional.
10
- path: 'media/background.png' // Supports PNG and JPG images. Use either 'image' or 'video', not both.
11
- },
12
- // OR use a video as background (optional):
13
- // video: {
14
- // path: 'media/background.mp4', // Supports MP4, MOV, AVI, MKV. Loops automatically if shorter than audio.
15
- // autoEdit: true, // Optional. Detects scene cuts and advances between them on the detected BPM beat grid about every 2–4 beats so the background edits with the music; omit or false for one continuous play-through from the start. When enabled, a brief camera shake every second beat decays over about one-third of a beat.
16
- // },
17
- audio: {
18
- path: 'media/audio.wav', // Supports MP3 and WAV audio
19
- autoHighlight: true,
20
- autoHighlightCount: 3, // Optional. With autoHighlight: how many non-overlapping 15s windows with the highest summed spectral energy to aim for (default 1; must be a whole number ≥ 1). You may get fewer videos if the track is too short or does not have that many valid windows. More than one window produces separate videos next to outVideo.path (e.g. out-1.mp4, out-2.mp4).
21
- },
22
- outVideo: {
23
- path: 'media/out.mp4',
24
- subtitles: { // Optional.
25
- path: 'media/subtitles.lrc', // Optional. .srt or .lrc format.
26
- rawContent: "1\n00:00:00,000 --> 99:59:59,999\nSome Artist - Some Song", // Only if path not specified.
27
- alignment: "middle", // Optional. Available values: 'top', 'middle', 'bottom'. Default: 'bottom'.
28
- },
29
- fps: 25, // Default value: 30
30
- resolution: { width: 1920, height: 1080 }, // Optional. Output frame size in pixels. Omit to keep the background image or video at its native resolution; when set, video frames are scaled to this size and images are resized (center-cropped first if landscape/portrait does not match the target aspect).
31
- spectrum: { // Audio spectrum configuration (linear bar visualizer). Optional. If neither 'spectrum' nor 'polar' is specified, no visualizer is drawn.
32
- width: '30%', // 30% of background image width. Default value: 33%. Also you can use absolute values. For example, value 300 for 300 pixels width.
33
- height: '15%', // 15% of background image height. Default value: 160. Also you can use absolute values. For example, value 300 for 300 pixels height.
34
- x: 'center', // Available values: 'left', 'center', right'. Also you can use absolute number values. For example, value 300 for 300 pixels x.
35
- y: 'bottom', // Available values: 'top', 'middle', bottom'. Also you can use absolute number values. For example, value 300 for 300 pixels y.
36
- rotation: 'mirror', // Available values: 'up', 'down', 'mirror'. Default value: 'mirror'.
37
- effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
38
- color: '#cccc99', // Default value: inverted color of background image
39
- opacity: '70%' // Default value: '80%'.
40
- },
41
- polar: { // Polar/circular audio spectrum configuration. Optional. Use either 'spectrum' or 'polar', not both.
42
- x: 'center', // Available values: 'left', 'center', 'right'. Also you can use absolute number values. Default value: 'center'.
43
- y: 'middle', // Available values: 'top', 'middle', 'bottom'. Also you can use absolute number values. Default value: 'middle'.
44
- innerRadius: 100, // Inner radius of the circular spectrum in pixels. Default value: 100.
45
- maxBarLength: 160, // Maximum bar length in pixels. Default value: 160.
46
- barWidth: 15, // Width of each bar in pixels. Default value: 15.
47
- effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
48
- color: '#cccc99', // Default value: inverted color of background image
49
- opacity: '70%' // Default value: '80%'.
50
- }
51
- },
52
- tweaks: { // Optional
53
- ffmpeg_cfr: '30', // Default value: 23
54
- ffmpeg_preset: 'ultrafast', // Default value: medium
55
- frame_processing_delay: 1000, // Delay between processing frames in milliseconds
56
- timeouts: { // Optional. FFmpeg I/O timeouts in milliseconds
57
- readVideoFrame: 30000, // Max wait for one background video frame from ffmpeg. Default: 30000
58
- waitDrain: 120000, // Max wait for ffmpeg stdin to accept more frames. Default: 120000
59
- waitForProcessExit: 480000 // Max wait for ffmpeg to finish after all frames are sent. Default: 480000
60
- }
61
- }
62
- };
63
-
64
- const onProgress = (progressPercent: number) => { // not necessary callback
65
- console.log(`progress: ${progressPercent} %`);
66
- };
67
-
68
- const shouldStop = () => { // not necessary callback
69
- return false;
70
- };
71
-
72
- renderAudioVisualizer(config, onProgress, shouldStop)
73
- .then(({ exitCode, outputVideoFiles }) => {
74
- console.log(`exited with code: ${exitCode}`);
75
- console.log(`written:`, outputVideoFiles);
76
- });
77
- ```
78
-
79
- ## Output preview
80
- ![frame of output video](example/media/out-sample.png)
1
+ # nodejs-audio-visualizer
2
+ Audio visualizer generator for Node.js with simple configuration.
3
+ Supports PNG, JPG for images, MP4/MOV/AVI/MKV for background videos, and MP3, WAV for audio.
4
+
5
+ ## Example
6
+ ```javascript
7
+ const renderAudioVisualizer = require('nodejs-audio-visualizer').renderAudioVisualizer;
8
+ const config = {
9
+ image: { // Optional.
10
+ path: 'media/background.png' // Supports PNG and JPG images. Use either 'image' or 'video', not both.
11
+ },
12
+ // OR use a video as background (optional):
13
+ // video: {
14
+ // path: 'media/background.mp4', // Supports MP4, MOV, AVI, MKV. Loops automatically if shorter than audio. Can also be an array of paths, e.g. ['media/bg1.mp4', 'media/bg2.mp4']: with autoEdit false the videos play one after another (looping the sequence as needed), and with autoHighlight producing several videos each one starts with a different video from the array; with autoEdit true, cuts switch (shuffle) between the given videos instead of auto-detecting scene changes. Videos in an array should share the same resolution and frame rate for best results, especially when outVideo.resolution is not set.
15
+ // autoEdit: true, // Optional. Detects scene cuts and advances between them on the detected BPM beat grid about every 2–4 beats so the background edits with the music; omit or false for one continuous play-through from the start. When enabled, a brief camera shake starts 0.1s after each cut and decays over about one-third of a beat (if the video has no detected scene changes, and so no cuts, it shakes every 2 beats instead).
16
+ // cameraShake: false, // Optional. With autoEdit: set to false to disable the camera shake applied on cuts (default true).
17
+ // },
18
+ audio: {
19
+ path: 'media/audio.wav', // Supports MP3 and WAV audio
20
+ autoHighlight: true, // Optional. Cuts the most energetic ~15s of the track. Start and end are moved onto beats (by up to 1.5s each), preferring a length of whole 4-beat bars, so the highlight may run slightly shorter or longer than 15s.
21
+ autoHighlightCount: 3, // Optional. With autoHighlight: how many non-overlapping ~15s windows with the highest summed spectral energy to aim for (default 1; must be a whole number ≥ 1). You may get fewer videos if the track is too short or does not have that many valid windows. More than one window produces separate videos next to outVideo.path (e.g. out-1.mp4, out-2.mp4).
22
+ },
23
+ outVideo: {
24
+ path: 'media/out.mp4',
25
+ subtitles: { // Optional.
26
+ path: 'media/subtitles.lrc', // Optional. .srt or .lrc format.
27
+ rawContent: "1\n00:00:00,000 --> 99:59:59,999\nSome Artist - Some Song", // Only if path not specified.
28
+ alignment: "middle", // Optional. Available values: 'top', 'middle', 'bottom'. Default: 'bottom'.
29
+ },
30
+ fps: 25, // Default value: 30
31
+ resolution: { width: 1920, height: 1080 }, // Optional. Output frame size in pixels. Omit to keep the background image or video at its native resolution; when set, video frames are scaled to this size and images are resized (center-cropped first if landscape/portrait does not match the target aspect).
32
+ spectrum: { // Audio spectrum configuration (linear bar visualizer). Optional. If neither 'spectrum' nor 'polar' is specified, no visualizer is drawn.
33
+ width: '30%', // 30% of background image width. Default value: 33%. Also you can use absolute values. For example, value 300 for 300 pixels width.
34
+ height: '15%', // 15% of background image height. Default value: 160. Also you can use absolute values. For example, value 300 for 300 pixels height.
35
+ x: 'center', // Available values: 'left', 'center', right'. Also you can use absolute number values. For example, value 300 for 300 pixels x.
36
+ y: 'bottom', // Available values: 'top', 'middle', bottom'. Also you can use absolute number values. For example, value 300 for 300 pixels y.
37
+ rotation: 'mirror', // Available values: 'up', 'down', 'mirror'. Default value: 'mirror'.
38
+ effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
39
+ color: '#cccc99', // Default value: inverted color of background image
40
+ opacity: '70%' // Default value: '80%'.
41
+ },
42
+ polar: { // Polar/circular audio spectrum configuration. Optional. Use either 'spectrum' or 'polar', not both.
43
+ x: 'center', // Available values: 'left', 'center', 'right'. Also you can use absolute number values. Default value: 'center'.
44
+ y: 'middle', // Available values: 'top', 'middle', 'bottom'. Also you can use absolute number values. Default value: 'middle'.
45
+ innerRadius: 100, // Inner radius of the circular spectrum in pixels. Default value: 100.
46
+ maxBarLength: 160, // Maximum bar length in pixels. Default value: 160.
47
+ barWidth: 15, // Width of each bar in pixels. Default value: 15.
48
+ effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
49
+ color: '#cccc99', // Default value: inverted color of background image
50
+ opacity: '70%' // Default value: '80%'.
51
+ }
52
+ },
53
+ tweaks: { // Optional
54
+ ffmpeg_cfr: '30', // Default value: 23
55
+ ffmpeg_preset: 'ultrafast', // Default value: medium
56
+ frame_processing_delay: 1000, // Delay between processing frames in milliseconds
57
+ timeouts: { // Optional. FFmpeg I/O timeouts in milliseconds
58
+ readVideoFrame: 30000, // Max wait for one background video frame from ffmpeg. Default: 30000
59
+ waitDrain: 120000, // Max wait for ffmpeg stdin to accept more frames. Default: 120000
60
+ waitForProcessExit: 480000 // Max wait for ffmpeg to finish after all frames are sent. Default: 480000
61
+ }
62
+ }
63
+ };
64
+
65
+ const onProgress = (progressPercent: number) => { // not necessary callback
66
+ console.log(`progress: ${progressPercent} %`);
67
+ };
68
+
69
+ const shouldStop = () => { // not necessary callback
70
+ return false;
71
+ };
72
+
73
+ renderAudioVisualizer(config, onProgress, shouldStop)
74
+ .then(({ exitCode, outputVideoFiles }) => {
75
+ console.log(`exited with code: ${exitCode}`);
76
+ console.log(`written:`, outputVideoFiles);
77
+ });
78
+ ```
79
+
80
+ ## Output preview
81
+ ![frame of output video](example/media/out-sample.png)
package/dist/audio.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- /// <reference types="node" />
2
- export declare const skipEvery: <T>(skipIndex: number) => (element: T, index: number) => boolean;
3
- export declare const getPeaks: (spectrums: number[], prevPeaks?: number[] | undefined) => number[];
4
- export declare const correctPeaks: (spectrums: number[], peaks: number[]) => number[];
5
- export declare const createSpectrumsProcessor: (sampleRate: number, skipFramesCount: number) => (parseAudioData: () => number[]) => number[];
6
- export declare const bufferToUInt8: (buffer: Buffer, start: number, end: number) => number[];
7
- export declare const normalizeAudioData: (PCMData: number[]) => number[];
8
- export declare const pcmU8ToFloatSamples: (buffer: Buffer) => number[];
9
- export declare const spawnFfmpegAudioReader: (filename: string, format: string) => import("child_process").ChildProcessWithoutNullStreams;
10
- export declare const createAudioBuffer: (filename: string, format: string) => Promise<{
11
- audioBuffer: Buffer;
12
- sampleRate: number;
13
- }>;
1
+ /// <reference types="node" />
2
+ export declare const skipEvery: <T>(skipIndex: number) => (element: T, index: number) => boolean;
3
+ export declare const getPeaks: (spectrums: number[], prevPeaks?: number[] | undefined) => number[];
4
+ export declare const correctPeaks: (spectrums: number[], peaks: number[]) => number[];
5
+ export declare const createSpectrumsProcessor: (sampleRate: number, skipFramesCount: number) => (parseAudioData: () => number[]) => number[];
6
+ export declare const bufferToUInt8: (buffer: Buffer, start: number, end: number) => number[];
7
+ export declare const normalizeAudioData: (PCMData: number[]) => number[];
8
+ export declare const pcmU8ToFloatSamples: (buffer: Buffer) => number[];
9
+ export declare const spawnFfmpegAudioReader: (filename: string, format: string) => import("child_process").ChildProcessWithoutNullStreams;
10
+ export declare const createAudioBuffer: (filename: string, format: string) => Promise<{
11
+ audioBuffer: Buffer;
12
+ sampleRate: number;
13
+ }>;
package/dist/audio.js CHANGED
@@ -1,129 +1,129 @@
1
- "use strict";
2
- var __spreadArrays = (this && this.__spreadArrays) || function () {
3
- for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
4
- for (var r = Array(s), k = 0, i = 0; i < il; i++)
5
- for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
6
- r[k] = a[j];
7
- return r;
8
- };
9
- var __importDefault = (this && this.__importDefault) || function (mod) {
10
- return (mod && mod.__esModule) ? mod : { "default": mod };
11
- };
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- var child_process_1 = require("child_process");
14
- var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
15
- var dsp_1 = require("./dsp");
16
- exports.skipEvery = function (skipIndex) { return function (element, index) {
17
- return index % skipIndex === 0;
18
- }; };
19
- exports.getPeaks = function (spectrums, prevPeaks) {
20
- if (!prevPeaks) {
21
- return spectrums;
22
- }
23
- var resultPeaks = [];
24
- for (var i = 0; i < spectrums.length; i++) {
25
- var currValue = spectrums[i];
26
- var currPrevPeak = prevPeaks[i] || 0;
27
- resultPeaks.push((currValue > currPrevPeak) ? currValue : currPrevPeak);
28
- }
29
- return resultPeaks;
30
- };
31
- exports.correctPeaks = function (spectrums, peaks) {
32
- var resultSpectrum = [];
33
- for (var i = 0; i < spectrums.length; i++) {
34
- var value = spectrums[i];
35
- var peakValue = peaks[i] || 0;
36
- if (value < 3) {
37
- resultSpectrum.push(value / 3);
38
- }
39
- else {
40
- resultSpectrum.push(value / peakValue);
41
- }
42
- }
43
- return resultSpectrum;
44
- };
45
- var oldMax = 1;
46
- var newMin = 0.6;
47
- var getInterpolationFactor = function (skippedFramesCount, skipFramesCount) {
48
- var interpolationFactor = 1 - (skippedFramesCount / skipFramesCount);
49
- var interpolationFactorInRange = (interpolationFactor * (oldMax - newMin)) + newMin;
50
- return interpolationFactorInRange;
51
- };
52
- exports.createSpectrumsProcessor = function (sampleRate, skipFramesCount) {
53
- var prevAudioDataNormalized = [];
54
- var prevPeaks = [];
55
- var skippedFramesCount = 0;
56
- var targetSpectrum = [];
57
- var previousSpectrum = [];
58
- return function (parseAudioData) {
59
- if (skippedFramesCount === 0) {
60
- skippedFramesCount = skipFramesCount;
61
- var audioDataNomrmalized = exports.normalizeAudioData(parseAudioData());
62
- prevAudioDataNormalized = audioDataNomrmalized;
63
- var spectrum = dsp_1.getSpectrum(audioDataNomrmalized, sampleRate);
64
- var peaks = exports.getPeaks(spectrum, prevPeaks);
65
- var correctedSpectrum = prevPeaks.length === 0 ?
66
- Array(dsp_1.FREQUENCY_BANDS.length).fill(0) :
67
- exports.correctPeaks(spectrum, peaks);
68
- prevPeaks = peaks;
69
- previousSpectrum = __spreadArrays(targetSpectrum);
70
- targetSpectrum = correctedSpectrum;
71
- }
72
- else {
73
- skippedFramesCount--;
74
- }
75
- var interpolationFactor = getInterpolationFactor(skippedFramesCount, skipFramesCount);
76
- var interpolatedSpectrum = targetSpectrum.map(function (target, i) {
77
- var prev = previousSpectrum[i] || target;
78
- return prev + (target - prev) * interpolationFactor;
79
- });
80
- return interpolatedSpectrum;
81
- };
82
- };
83
- exports.bufferToUInt8 = function (buffer, start, end) {
84
- var numbers = [];
85
- for (var i = start; i < end; i += 1) {
86
- numbers.push(buffer.readUInt8(i));
87
- }
88
- return numbers;
89
- };
90
- exports.normalizeAudioData = function (PCMData) {
91
- return PCMData.map(function (num) { return (num - 128) / 128; });
92
- };
93
- exports.pcmU8ToFloatSamples = function (buffer) {
94
- var n = buffer.length;
95
- var samples = new Array(n);
96
- for (var i = 0; i < n; i++) {
97
- samples[i] = (buffer[i] - 128) / 128;
98
- }
99
- return samples;
100
- };
101
- exports.spawnFfmpegAudioReader = function (filename, format) {
102
- if (!ffmpeg_static_1.default) {
103
- throw new Error('ffmpeg path not found');
104
- }
105
- var ffmpegProcess = child_process_1.spawn(ffmpeg_static_1.default, ['-i', filename, '-f', format, '-ac', '1', '-']);
106
- return ffmpegProcess;
107
- };
108
- exports.createAudioBuffer = function (filename, format) {
109
- return new Promise(function (resolve, reject) {
110
- var sampleRate;
111
- var sampleRateRegExp = /(\d+) Hz/m;
112
- var audioBuffers = [];
113
- var ffmpegAudioReader = exports.spawnFfmpegAudioReader(filename, format);
114
- ffmpegAudioReader.stderr.on('data', function (data) {
115
- var match = data.toString().match(sampleRateRegExp);
116
- if (!sampleRate && match) {
117
- sampleRate = match[1];
118
- }
119
- });
120
- ffmpegAudioReader.stdout.on('data', function (chunkBuffer) {
121
- audioBuffers.push(chunkBuffer);
122
- });
123
- ffmpegAudioReader.stdout.on('end', function () {
124
- var audioBuffer = Buffer.concat(audioBuffers);
125
- resolve({ audioBuffer: audioBuffer, sampleRate: sampleRate });
126
- });
127
- });
128
- };
1
+ "use strict";
2
+ var __spreadArrays = (this && this.__spreadArrays) || function () {
3
+ for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
4
+ for (var r = Array(s), k = 0, i = 0; i < il; i++)
5
+ for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
6
+ r[k] = a[j];
7
+ return r;
8
+ };
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ var child_process_1 = require("child_process");
14
+ var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
15
+ var dsp_1 = require("./dsp");
16
+ exports.skipEvery = function (skipIndex) { return function (element, index) {
17
+ return index % skipIndex === 0;
18
+ }; };
19
+ exports.getPeaks = function (spectrums, prevPeaks) {
20
+ if (!prevPeaks) {
21
+ return spectrums;
22
+ }
23
+ var resultPeaks = [];
24
+ for (var i = 0; i < spectrums.length; i++) {
25
+ var currValue = spectrums[i];
26
+ var currPrevPeak = prevPeaks[i] || 0;
27
+ resultPeaks.push((currValue > currPrevPeak) ? currValue : currPrevPeak);
28
+ }
29
+ return resultPeaks;
30
+ };
31
+ exports.correctPeaks = function (spectrums, peaks) {
32
+ var resultSpectrum = [];
33
+ for (var i = 0; i < spectrums.length; i++) {
34
+ var value = spectrums[i];
35
+ var peakValue = peaks[i] || 0;
36
+ if (value < 3) {
37
+ resultSpectrum.push(value / 3);
38
+ }
39
+ else {
40
+ resultSpectrum.push(value / peakValue);
41
+ }
42
+ }
43
+ return resultSpectrum;
44
+ };
45
+ var oldMax = 1;
46
+ var newMin = 0.6;
47
+ var getInterpolationFactor = function (skippedFramesCount, skipFramesCount) {
48
+ var interpolationFactor = 1 - (skippedFramesCount / skipFramesCount);
49
+ var interpolationFactorInRange = (interpolationFactor * (oldMax - newMin)) + newMin;
50
+ return interpolationFactorInRange;
51
+ };
52
+ exports.createSpectrumsProcessor = function (sampleRate, skipFramesCount) {
53
+ var prevAudioDataNormalized = [];
54
+ var prevPeaks = [];
55
+ var skippedFramesCount = 0;
56
+ var targetSpectrum = [];
57
+ var previousSpectrum = [];
58
+ return function (parseAudioData) {
59
+ if (skippedFramesCount === 0) {
60
+ skippedFramesCount = skipFramesCount;
61
+ var audioDataNomrmalized = exports.normalizeAudioData(parseAudioData());
62
+ prevAudioDataNormalized = audioDataNomrmalized;
63
+ var spectrum = dsp_1.getSpectrum(audioDataNomrmalized, sampleRate);
64
+ var peaks = exports.getPeaks(spectrum, prevPeaks);
65
+ var correctedSpectrum = prevPeaks.length === 0 ?
66
+ Array(dsp_1.FREQUENCY_BANDS.length).fill(0) :
67
+ exports.correctPeaks(spectrum, peaks);
68
+ prevPeaks = peaks;
69
+ previousSpectrum = __spreadArrays(targetSpectrum);
70
+ targetSpectrum = correctedSpectrum;
71
+ }
72
+ else {
73
+ skippedFramesCount--;
74
+ }
75
+ var interpolationFactor = getInterpolationFactor(skippedFramesCount, skipFramesCount);
76
+ var interpolatedSpectrum = targetSpectrum.map(function (target, i) {
77
+ var prev = previousSpectrum[i] || target;
78
+ return prev + (target - prev) * interpolationFactor;
79
+ });
80
+ return interpolatedSpectrum;
81
+ };
82
+ };
83
+ exports.bufferToUInt8 = function (buffer, start, end) {
84
+ var numbers = [];
85
+ for (var i = start; i < end; i += 1) {
86
+ numbers.push(buffer.readUInt8(i));
87
+ }
88
+ return numbers;
89
+ };
90
+ exports.normalizeAudioData = function (PCMData) {
91
+ return PCMData.map(function (num) { return (num - 128) / 128; });
92
+ };
93
+ exports.pcmU8ToFloatSamples = function (buffer) {
94
+ var n = buffer.length;
95
+ var samples = new Array(n);
96
+ for (var i = 0; i < n; i++) {
97
+ samples[i] = (buffer[i] - 128) / 128;
98
+ }
99
+ return samples;
100
+ };
101
+ exports.spawnFfmpegAudioReader = function (filename, format) {
102
+ if (!ffmpeg_static_1.default) {
103
+ throw new Error('ffmpeg path not found');
104
+ }
105
+ var ffmpegProcess = child_process_1.spawn(ffmpeg_static_1.default, ['-i', filename, '-f', format, '-ac', '1', '-']);
106
+ return ffmpegProcess;
107
+ };
108
+ exports.createAudioBuffer = function (filename, format) {
109
+ return new Promise(function (resolve, reject) {
110
+ var sampleRate;
111
+ var sampleRateRegExp = /(\d+) Hz/m;
112
+ var audioBuffers = [];
113
+ var ffmpegAudioReader = exports.spawnFfmpegAudioReader(filename, format);
114
+ ffmpegAudioReader.stderr.on('data', function (data) {
115
+ var match = data.toString().match(sampleRateRegExp);
116
+ if (!sampleRate && match) {
117
+ sampleRate = match[1];
118
+ }
119
+ });
120
+ ffmpegAudioReader.stdout.on('data', function (chunkBuffer) {
121
+ audioBuffers.push(chunkBuffer);
122
+ });
123
+ ffmpegAudioReader.stdout.on('end', function () {
124
+ var audioBuffer = Buffer.concat(audioBuffers);
125
+ resolve({ audioBuffer: audioBuffer, sampleRate: sampleRate });
126
+ });
127
+ });
128
+ };
129
129
  //# sourceMappingURL=audio.js.map
package/dist/beats.d.ts CHANGED
@@ -1,39 +1,39 @@
1
- export interface BeatInfo {
2
- isBeat: boolean;
3
- intensity: number;
4
- energy: number;
5
- threshold: number;
6
- }
7
- export interface BeatDetectorOptions {
8
- /** Number of past frames to average for adaptive threshold (~1.5s by default) */
9
- historySize?: number;
10
- /** Energy must exceed average * this multiplier to trigger a beat */
11
- thresholdMultiplier?: number;
12
- /** How fast the beat intensity fades between beats (0-1, lower = faster fade) */
13
- decayRate?: number;
14
- /** Minimum frames between consecutive beats (~150ms by default) */
15
- cooldownFrames?: number;
16
- /** How many low-frequency spectrum bands contribute to energy (sub-bass + bass) */
17
- bassEndIndex?: number;
18
- }
19
- export interface TempoEstimate {
20
- bpm: number;
21
- periodFrames: number;
22
- phaseFrame: number;
23
- /** Beat times in seconds from the start of the analyzed audio. */
24
- beatsSec?: number[];
25
- }
26
- export declare const MIN_TEMPO_BPM = 70;
27
- export declare const MAX_TEMPO_BPM = 180;
28
- export declare const PREFERRED_TEMPO_BPM_MIN = 90;
29
- export declare const PREFERRED_TEMPO_BPM_MAX = 160;
30
- export declare const DEFAULT_BASS_END_INDEX = 8;
31
- /** Longest PCM window passed to music-tempo (seconds), taken from the start of the audio. */
32
- export declare const MAX_TEMPO_ANALYSIS_SECONDS = 90;
33
- export declare const frameBassEnergy: (spectrum: number[], bassEndIndex?: number) => number;
34
- export declare const createBeatDetector: (fps: number, options?: BeatDetectorOptions | undefined) => (spectrum: number[]) => BeatInfo;
35
- export declare const estimateTempo: (samples: ArrayLike<number>, sampleRate: number, fps: number) => TempoEstimate | null;
36
- export declare const shiftTempoPhase: (tempo: TempoEstimate, startFrame: number) => TempoEstimate;
37
- /** Beat-grid phase for a later audio window, using in-window beat times when available. */
38
- export declare const tempoForWindow: (tempo: TempoEstimate, startFrame: number, fps: number, windowFrames?: number | undefined, localOnsetFrames?: number[] | undefined) => TempoEstimate;
39
- export declare const beatGridFrameIndices: (tempo: Pick<TempoEstimate, "periodFrames" | "phaseFrame">, totalFrames: number, stride?: number) => number[];
1
+ export interface BeatInfo {
2
+ isBeat: boolean;
3
+ intensity: number;
4
+ energy: number;
5
+ threshold: number;
6
+ }
7
+ export interface BeatDetectorOptions {
8
+ /** Number of past frames to average for adaptive threshold (~1.5s by default) */
9
+ historySize?: number;
10
+ /** Energy must exceed average * this multiplier to trigger a beat */
11
+ thresholdMultiplier?: number;
12
+ /** How fast the beat intensity fades between beats (0-1, lower = faster fade) */
13
+ decayRate?: number;
14
+ /** Minimum frames between consecutive beats (~150ms by default) */
15
+ cooldownFrames?: number;
16
+ /** How many low-frequency spectrum bands contribute to energy (sub-bass + bass) */
17
+ bassEndIndex?: number;
18
+ }
19
+ export interface TempoEstimate {
20
+ bpm: number;
21
+ periodFrames: number;
22
+ phaseFrame: number;
23
+ /** Beat times in seconds from the start of the analyzed audio. */
24
+ beatsSec?: number[];
25
+ }
26
+ export declare const MIN_TEMPO_BPM = 70;
27
+ export declare const MAX_TEMPO_BPM = 180;
28
+ export declare const PREFERRED_TEMPO_BPM_MIN = 90;
29
+ export declare const PREFERRED_TEMPO_BPM_MAX = 160;
30
+ export declare const DEFAULT_BASS_END_INDEX = 8;
31
+ /** Longest PCM window passed to music-tempo (seconds), taken from the start of the audio. */
32
+ export declare const MAX_TEMPO_ANALYSIS_SECONDS = 90;
33
+ export declare const frameBassEnergy: (spectrum: number[], bassEndIndex?: number) => number;
34
+ export declare const createBeatDetector: (fps: number, options?: BeatDetectorOptions | undefined) => (spectrum: number[]) => BeatInfo;
35
+ export declare const estimateTempo: (samples: ArrayLike<number>, sampleRate: number, fps: number) => TempoEstimate | null;
36
+ export declare const shiftTempoPhase: (tempo: TempoEstimate, startFrame: number) => TempoEstimate;
37
+ /** Beat-grid phase for a later audio window, using in-window beat times when available. */
38
+ export declare const tempoForWindow: (tempo: TempoEstimate, startFrame: number, fps: number, windowFrames?: number | undefined, localOnsetFrames?: number[] | undefined) => TempoEstimate;
39
+ export declare const beatGridFrameIndices: (tempo: Pick<TempoEstimate, "periodFrames" | "phaseFrame">, totalFrames: number, stride?: number) => number[];