nodejs-audio-visualizer 5.5.3 → 5.6.3

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 (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +84 -83
  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 +81 -80
  10. package/dist/config.js +392 -391
  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 +91 -91
  15. package/dist/highlight.js +606 -606
  16. package/dist/image.d.ts +107 -97
  17. package/dist/image.js +614 -545
  18. package/dist/image.js.map +1 -1
  19. package/dist/index.d.ts +98 -96
  20. package/dist/index.js +673 -667
  21. package/dist/index.js.map +1 -1
  22. package/dist/subtitleConvert.d.ts +3 -3
  23. package/dist/subtitleConvert.js +61 -61
  24. package/dist/video.d.ts +171 -171
  25. package/dist/video.js +743 -743
  26. package/dist/waitForEventLoop.d.ts +1 -1
  27. package/dist/waitForEventLoop.js +5 -5
  28. package/package.json +62 -62
  29. package/src/audio.ts +129 -129
  30. package/src/beats.ts +279 -279
  31. package/src/bpmEncoder.ts +97 -97
  32. package/src/config.ts +504 -501
  33. package/src/dsp.ts +95 -95
  34. package/src/highlight.ts +617 -617
  35. package/src/image.ts +791 -707
  36. package/src/index.ts +896 -885
  37. package/src/subtitleConvert.ts +63 -63
  38. package/src/tests/audio.test.ts +102 -102
  39. package/src/tests/beats.test.ts +134 -134
  40. package/src/tests/config.test.ts +568 -553
  41. package/src/tests/dsp.test.ts +24 -24
  42. package/src/tests/highlight.test.ts +397 -397
  43. package/src/tests/image.test.ts +371 -319
  44. package/src/tests/index.test.ts +180 -180
  45. package/src/tests/subtitleConvert.test.ts +23 -23
  46. package/src/tests/video.test.ts +652 -652
  47. package/src/typings.d.ts +28 -28
  48. package/src/video.ts +947 -947
  49. 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,83 +1,84 @@
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
- // hookPath: 'media/hook.mp4', // Optional. Video played once before the 'path' video(s). Plays in full; with audio.autoHighlight, each highlight is chosen so the hook ends on a detected drop (the biggest jump in energy on a beat) and 'path' starts with it and plays for ~15s (the hook is added before the ~15s, not counted in it); the whole hook always plays (when the drop is closer to the track start than the hook's length, silence is added before the track), and if no drop fits, the hook plays before the loudest ~15s window instead. The hook's sound, if it has any, is mixed over the track, which fades in from silence to full volume while the hook plays. With several highlight videos, each one opens with the hook.
16
- // 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).
17
- // cameraShake: false, // Optional. With autoEdit: set to false to disable the camera shake applied on cuts (default true).
18
- // },
19
- audio: {
20
- path: 'media/audio.wav', // Supports MP3 and WAV audio
21
- 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.
22
- 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).
23
- },
24
- outVideo: {
25
- path: 'media/out.mp4',
26
- subtitles: { // Optional.
27
- path: 'media/subtitles.lrc', // Optional. .srt or .lrc format.
28
- rawContent: "1\n00:00:00,000 --> 99:59:59,999\nSome Artist - Some Song", // Only if path not specified.
29
- alignment: "middle", // Optional. Available values: 'top', 'middle', 'bottom'. Default: 'bottom'.
30
- },
31
- fps: 25, // Default value: 30
32
- 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).
33
- enhanceFilters: true, // Optional. Denoise, sharpen and boost contrast/saturation of the output video (FFmpeg hqdn3d, unsharp, eq; Lanczos scaling). Default: false
34
- spectrum: { // Audio spectrum configuration (linear bar visualizer). Optional. If neither 'spectrum' nor 'polar' is specified, no visualizer is drawn.
35
- width: '30%', // 30% of background image width. Default value: 33%. Also you can use absolute values. For example, value 300 for 300 pixels width.
36
- height: '15%', // 15% of background image height. Default value: 160. Also you can use absolute values. For example, value 300 for 300 pixels height.
37
- x: 'center', // Available values: 'left', 'center', right'. Also you can use absolute number values. For example, value 300 for 300 pixels x.
38
- y: 'bottom', // Available values: 'top', 'middle', bottom'. Also you can use absolute number values. For example, value 300 for 300 pixels y.
39
- rotation: 'mirror', // Available values: 'up', 'down', 'mirror'. Default value: 'mirror'.
40
- effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
41
- color: '#cccc99', // Default value: inverted color of background image
42
- opacity: '70%' // Default value: '80%'.
43
- },
44
- polar: { // Polar/circular audio spectrum configuration. Optional. Use either 'spectrum' or 'polar', not both.
45
- x: 'center', // Available values: 'left', 'center', 'right'. Also you can use absolute number values. Default value: 'center'.
46
- y: 'middle', // Available values: 'top', 'middle', 'bottom'. Also you can use absolute number values. Default value: 'middle'.
47
- innerRadius: 100, // Inner radius of the circular spectrum in pixels. Default value: 100.
48
- maxBarLength: 160, // Maximum bar length in pixels. Default value: 160.
49
- barWidth: 15, // Width of each bar in pixels. Default value: 15.
50
- effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
51
- color: '#cccc99', // Default value: inverted color of background image
52
- opacity: '70%' // Default value: '80%'.
53
- }
54
- },
55
- tweaks: { // Optional
56
- ffmpeg_cfr: '30', // Default value: 23
57
- ffmpeg_preset: 'ultrafast', // Default value: medium
58
- frame_processing_delay: 1000, // Delay between processing frames in milliseconds
59
- timeouts: { // Optional. FFmpeg I/O timeouts in milliseconds
60
- readVideoFrame: 30000, // Max wait for one background video frame from ffmpeg. Default: 30000
61
- waitDrain: 120000, // Max wait for ffmpeg stdin to accept more frames. Default: 120000
62
- waitForProcessExit: 480000 // Max wait for ffmpeg to finish after all frames are sent. Default: 480000
63
- }
64
- }
65
- };
66
-
67
- const onProgress = (progressPercent: number) => { // not necessary callback
68
- console.log(`progress: ${progressPercent} %`);
69
- };
70
-
71
- const shouldStop = () => { // not necessary callback
72
- return false;
73
- };
74
-
75
- renderAudioVisualizer(config, onProgress, shouldStop)
76
- .then(({ exitCode, outputVideoFiles }) => {
77
- console.log(`exited with code: ${exitCode}`);
78
- console.log(`written:`, outputVideoFiles);
79
- });
80
- ```
81
-
82
- ## Output preview
83
- ![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
+ // hookPath: 'media/hook.mp4', // Optional. Video played once before the 'path' video(s). Plays in full; with audio.autoHighlight, each highlight is chosen so the hook ends on a detected drop (the biggest jump in energy on a beat) and 'path' starts with it and plays for ~15s (the hook is added before the ~15s, not counted in it); the whole hook always plays (when the drop is closer to the track start than the hook's length, silence is added before the track), and if no drop fits, the hook plays before the loudest ~15s window instead. The hook's sound, if it has any, is mixed over the track, which fades in from silence to full volume while the hook plays. With several highlight videos, each one opens with the hook.
16
+ // 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).
17
+ // cameraShake: false, // Optional. With autoEdit: set to false to disable the camera shake applied on cuts (default true).
18
+ // cutZoom: true, // Optional. With autoEdit: starts each cut zoomed in (115%) and quickly zooms out to normal over about one-third of a beat (every 2 beats when there are no cuts). Can be combined with cameraShake (default false).
19
+ // },
20
+ audio: {
21
+ path: 'media/audio.wav', // Supports MP3 and WAV audio
22
+ 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.
23
+ 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).
24
+ },
25
+ outVideo: {
26
+ path: 'media/out.mp4',
27
+ subtitles: { // Optional.
28
+ path: 'media/subtitles.lrc', // Optional. .srt or .lrc format.
29
+ rawContent: "1\n00:00:00,000 --> 99:59:59,999\nSome Artist - Some Song", // Only if path not specified.
30
+ alignment: "middle", // Optional. Available values: 'top', 'middle', 'bottom'. Default: 'bottom'.
31
+ },
32
+ fps: 25, // Default value: 30
33
+ 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).
34
+ enhanceFilters: true, // Optional. Denoise, sharpen and boost contrast/saturation of the output video (FFmpeg hqdn3d, unsharp, eq; Lanczos scaling). Default: false
35
+ spectrum: { // Audio spectrum configuration (linear bar visualizer). Optional. If neither 'spectrum' nor 'polar' is specified, no visualizer is drawn.
36
+ width: '30%', // 30% of background image width. Default value: 33%. Also you can use absolute values. For example, value 300 for 300 pixels width.
37
+ height: '15%', // 15% of background image height. Default value: 160. Also you can use absolute values. For example, value 300 for 300 pixels height.
38
+ x: 'center', // Available values: 'left', 'center', right'. Also you can use absolute number values. For example, value 300 for 300 pixels x.
39
+ y: 'bottom', // Available values: 'top', 'middle', bottom'. Also you can use absolute number values. For example, value 300 for 300 pixels y.
40
+ rotation: 'mirror', // Available values: 'up', 'down', 'mirror'. Default value: 'mirror'.
41
+ effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
42
+ color: '#cccc99', // Default value: inverted color of background image
43
+ opacity: '70%' // Default value: '80%'.
44
+ },
45
+ polar: { // Polar/circular audio spectrum configuration. Optional. Use either 'spectrum' or 'polar', not both.
46
+ x: 'center', // Available values: 'left', 'center', 'right'. Also you can use absolute number values. Default value: 'center'.
47
+ y: 'middle', // Available values: 'top', 'middle', 'bottom'. Also you can use absolute number values. Default value: 'middle'.
48
+ innerRadius: 100, // Inner radius of the circular spectrum in pixels. Default value: 100.
49
+ maxBarLength: 160, // Maximum bar length in pixels. Default value: 160.
50
+ barWidth: 15, // Width of each bar in pixels. Default value: 15.
51
+ effect: 'volume', // Available values: 'volume', 'smooth', undefined. Default value: undefined.
52
+ color: '#cccc99', // Default value: inverted color of background image
53
+ opacity: '70%' // Default value: '80%'.
54
+ }
55
+ },
56
+ tweaks: { // Optional
57
+ ffmpeg_cfr: '30', // Default value: 23
58
+ ffmpeg_preset: 'ultrafast', // Default value: medium
59
+ frame_processing_delay: 1000, // Delay between processing frames in milliseconds
60
+ timeouts: { // Optional. FFmpeg I/O timeouts in milliseconds
61
+ readVideoFrame: 30000, // Max wait for one background video frame from ffmpeg. Default: 30000
62
+ waitDrain: 120000, // Max wait for ffmpeg stdin to accept more frames. Default: 120000
63
+ waitForProcessExit: 480000 // Max wait for ffmpeg to finish after all frames are sent. Default: 480000
64
+ }
65
+ }
66
+ };
67
+
68
+ const onProgress = (progressPercent: number) => { // not necessary callback
69
+ console.log(`progress: ${progressPercent} %`);
70
+ };
71
+
72
+ const shouldStop = () => { // not necessary callback
73
+ return false;
74
+ };
75
+
76
+ renderAudioVisualizer(config, onProgress, shouldStop)
77
+ .then(({ exitCode, outputVideoFiles }) => {
78
+ console.log(`exited with code: ${exitCode}`);
79
+ console.log(`written:`, outputVideoFiles);
80
+ });
81
+ ```
82
+
83
+ ## Output preview
84
+ ![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[];