nodejs-audio-visualizer 5.4.0 → 5.4.1

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/beats.d.ts CHANGED
@@ -23,15 +23,20 @@ export interface TempoEstimate {
23
23
  /** Beat times in seconds from the start of the analyzed audio. */
24
24
  beatsSec?: number[];
25
25
  }
26
- export declare const MIN_TEMPO_BPM = 70;
26
+ /** Detected tempo is folded (doubled/halved) into this range. */
27
+ export declare const MIN_TEMPO_BPM = 90;
27
28
  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
29
  export declare const DEFAULT_BASS_END_INDEX = 8;
31
- /** Longest PCM window passed to music-tempo (seconds), taken from the start of the audio. */
30
+ /** Longest PCM window used for tempo analysis (seconds), taken from the start of the audio. */
32
31
  export declare const MAX_TEMPO_ANALYSIS_SECONDS = 90;
33
32
  export declare const frameBassEnergy: (spectrum: number[], bassEndIndex?: number) => number;
34
33
  export declare const createBeatDetector: (fps: number, options?: BeatDetectorOptions | undefined) => (spectrum: number[]) => BeatInfo;
34
+ /**
35
+ * Estimates the track tempo and beat phase (after BeatDetect.js by Arthur Beaulieu):
36
+ * band-pass the audio around the kick, take the loudest sample of every half second,
37
+ * keep the loudest half of those peaks, and pick the most frequent tempo among the intervals
38
+ * between neighbouring peaks. The beat phase comes from the loudest peak and the peaks aligned with it.
39
+ */
35
40
  export declare const estimateTempo: (samples: ArrayLike<number>, sampleRate: number, fps: number) => TempoEstimate | null;
36
41
  export declare const shiftTempoPhase: (tempo: TempoEstimate, startFrame: number) => TempoEstimate;
37
42
  /** Beat-grid phase for a later audio window, using in-window beat times when available. */
package/dist/beats.js CHANGED
@@ -10,19 +10,27 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
13
  Object.defineProperty(exports, "__esModule", { value: true });
17
- var music_tempo_1 = __importDefault(require("music-tempo"));
18
- exports.MIN_TEMPO_BPM = 70;
14
+ /** Detected tempo is folded (doubled/halved) into this range. */
15
+ exports.MIN_TEMPO_BPM = 90;
19
16
  exports.MAX_TEMPO_BPM = 180;
20
- exports.PREFERRED_TEMPO_BPM_MIN = 90;
21
- exports.PREFERRED_TEMPO_BPM_MAX = 160;
22
17
  exports.DEFAULT_BASS_END_INDEX = 8;
23
- /** Longest PCM window passed to music-tempo (seconds), taken from the start of the audio. */
18
+ /** Longest PCM window used for tempo analysis (seconds), taken from the start of the audio. */
24
19
  exports.MAX_TEMPO_ANALYSIS_SECONDS = 90;
25
- var TEMPO_TIME_STEP = 0.01;
20
+ /** Band-pass (low pass then high pass) that isolates kick impulses before peak picking. */
21
+ var TEMPO_LOW_PASS_HZ = 150;
22
+ var TEMPO_HIGH_PASS_HZ = 100;
23
+ /** Biquad Q in dB, as in the Web Audio BiquadFilterNode. */
24
+ var TEMPO_FILTER_Q_DB = 1;
25
+ /** Audio is split into parts of this length; each part contributes its loudest sample as a peak. */
26
+ var TEMPO_PART_SECONDS = 0.5;
27
+ /** Each peak is compared with this many following peaks to build intervals. */
28
+ var TEMPO_INTERVAL_NEIGHBOURS = 9;
29
+ /** Peaks are moved left by this share of a beat to land on the start of the hit, not its maximum. */
30
+ var TEMPO_PEAK_LEFT_SHIFT_SHARE = 0.05;
31
+ /** Peaks whose beat offset is within this many seconds of the loudest peak's offset set the phase. */
32
+ var TEMPO_OFFSET_TOLERANCE_SEC = 0.05;
33
+ var SILENCE_THRESHOLD = 1e-4;
26
34
  exports.frameBassEnergy = function (spectrum, bassEndIndex) {
27
35
  if (bassEndIndex === void 0) { bassEndIndex = exports.DEFAULT_BASS_END_INDEX; }
28
36
  var end = Math.min(bassEndIndex, spectrum.length);
@@ -76,27 +84,6 @@ exports.createBeatDetector = function (fps, options) {
76
84
  };
77
85
  };
78
86
  };
79
- var foldTempoBpm = function (bpm) {
80
- var folded = bpm;
81
- while (folded > exports.MAX_TEMPO_BPM && folded / 2 >= exports.MIN_TEMPO_BPM) {
82
- folded /= 2;
83
- }
84
- while (folded < exports.MIN_TEMPO_BPM && folded * 2 <= exports.MAX_TEMPO_BPM) {
85
- folded *= 2;
86
- }
87
- return folded;
88
- };
89
- var samplesToArray = function (samples, maxLength) {
90
- var n = Math.min(samples.length, maxLength);
91
- if (Array.isArray(samples) && n === samples.length) {
92
- return samples;
93
- }
94
- var audioData = new Array(n);
95
- for (var i = 0; i < n; i++) {
96
- audioData[i] = samples[i];
97
- }
98
- return audioData;
99
- };
100
87
  var wrapIntoPeriod = function (value, period) {
101
88
  if (!(period > 0) || !isFinite(value)) {
102
89
  return 0;
@@ -125,47 +112,153 @@ var circularMeanPhase = function (frames, period) {
125
112
  var phase = Math.atan2(sinSum, cosSum) * period / (2 * Math.PI);
126
113
  return wrapIntoPeriod(phase, period);
127
114
  };
128
- exports.estimateTempo = function (samples, sampleRate, fps) {
129
- var _a;
130
- if (!samples || samples.length === 0 || !(sampleRate > 0) || !(fps > 0)) {
131
- return null;
115
+ /** Signed distance from `value` to `reference` on a circle of length `period`, in [-period/2, period/2). */
116
+ var circularDiff = function (value, reference, period) {
117
+ return wrapIntoPeriod(value - reference + period / 2, period) - period / 2;
118
+ };
119
+ /** Biquad filter with the Web Audio BiquadFilterNode (RBJ cookbook) coefficients. */
120
+ var biquadFilter = function (samples, length, sampleRate, type, frequency, qDb) {
121
+ var w0 = 2 * Math.PI * Math.min(frequency, sampleRate / 2 - 1) / sampleRate;
122
+ var cosW0 = Math.cos(w0);
123
+ var alpha = Math.sin(w0) / (2 * Math.pow(10, qDb / 20));
124
+ var a0 = 1 + alpha;
125
+ var b1 = (type === 'lowpass' ? 1 - cosW0 : -(1 + cosW0)) / a0;
126
+ var b0 = (type === 'lowpass' ? b1 / 2 : -b1 / 2);
127
+ var b2 = b0;
128
+ var a1 = (-2 * cosW0) / a0;
129
+ var a2 = (1 - alpha) / a0;
130
+ var out = new Float32Array(length);
131
+ var x1 = 0;
132
+ var x2 = 0;
133
+ var y1 = 0;
134
+ var y2 = 0;
135
+ for (var i = 0; i < length; i++) {
136
+ var x = samples[i];
137
+ var y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
138
+ out[i] = y;
139
+ x2 = x1;
140
+ x1 = x;
141
+ y2 = y1;
142
+ y1 = y;
132
143
  }
133
- var maxSamples = Math.max(1, Math.floor(sampleRate * exports.MAX_TEMPO_ANALYSIS_SECONDS));
134
- var audioData = samplesToArray(samples, maxSamples);
135
- var hopSize = Math.max(1, Math.round(sampleRate * TEMPO_TIME_STEP));
136
- try {
137
- var mt = new music_tempo_1.default(audioData, {
138
- hopSize: hopSize,
139
- timeStep: TEMPO_TIME_STEP,
140
- minBeatInterval: 60 / exports.MAX_TEMPO_BPM,
141
- maxBeatInterval: 60 / exports.MIN_TEMPO_BPM,
142
- });
143
- var beatInterval = Number(mt.beatInterval);
144
- var bpm = Number(mt.tempo);
145
- if (!(bpm > 0) && beatInterval > 0) {
146
- bpm = 60 / beatInterval;
144
+ return out;
145
+ };
146
+ /** Loudest sample of each part (`getVolume` picks how a sample is measured). */
147
+ var partPeaks = function (data, partSize, getVolume, positionShift) {
148
+ if (positionShift === void 0) { positionShift = 0; }
149
+ var peaks = [];
150
+ for (var start = 0; start < data.length; start += partSize) {
151
+ var end = Math.min(start + partSize, data.length);
152
+ var max = null;
153
+ for (var j = start; j < end; j++) {
154
+ var volume = getVolume(data[j]);
155
+ if (!max || volume > max.volume) {
156
+ max = { position: j - positionShift, volume: volume };
157
+ }
147
158
  }
148
- if (!(bpm > 0)) {
149
- return null;
159
+ if (max) {
160
+ peaks.push(max);
150
161
  }
151
- bpm = foldTempoBpm(bpm);
152
- if (bpm < exports.MIN_TEMPO_BPM * 0.9 || bpm > exports.MAX_TEMPO_BPM * 1.1) {
153
- return null;
162
+ }
163
+ return peaks;
164
+ };
165
+ /** Loudest half of the per-part peaks, in playback order. */
166
+ var getLoudPeaks = function (data, partSize) {
167
+ var peaks = partPeaks(data, partSize, Math.abs)
168
+ .sort(function (a, b) { return b.volume - a.volume; });
169
+ return peaks
170
+ .slice(0, Math.floor(peaks.length / 2))
171
+ .filter(function (p) { return p.volume > SILENCE_THRESHOLD; })
172
+ .sort(function (a, b) { return a.position - b.position; });
173
+ };
174
+ var foldTempoBpm = function (bpm) {
175
+ var folded = bpm;
176
+ while (folded <= exports.MIN_TEMPO_BPM) {
177
+ folded *= 2;
178
+ }
179
+ while (folded > exports.MAX_TEMPO_BPM) {
180
+ folded /= 2;
181
+ }
182
+ return folded;
183
+ };
184
+ /** Most frequent tempo among the intervals between each peak and its following neighbours. */
185
+ var getMostFrequentTempo = function (peaks, sampleRate) {
186
+ var counts = new Map();
187
+ peaks.forEach(function (peak, index) {
188
+ var _a;
189
+ for (var i = 1; index + i < peaks.length && i <= TEMPO_INTERVAL_NEIGHBOURS; i++) {
190
+ var distance = peaks[index + i].position - peak.position;
191
+ if (distance <= 0) {
192
+ continue;
193
+ }
194
+ var tempo = Math.round(foldTempoBpm((60 * sampleRate) / distance));
195
+ counts.set(tempo, (_a = counts.get(tempo), (_a !== null && _a !== void 0 ? _a : 0)) + 1);
196
+ }
197
+ });
198
+ var best = null;
199
+ var bestCount = 0;
200
+ counts.forEach(function (count, tempo) {
201
+ if (count > bestCount) {
202
+ best = tempo;
203
+ bestCount = count;
154
204
  }
155
- var periodFrames = fps * 60 / bpm;
156
- var beatsSec = (_a = mt.beats, (_a !== null && _a !== void 0 ? _a : []))
157
- .map(function (t) { return Number(t); })
158
- .filter(function (t) { return t >= 0 && isFinite(t); });
159
- var beatFrames = beatsSec.map(function (t) { return t * fps; });
160
- var phaseFrame = beatFrames.length > 0
161
- ? wrapIntoPeriod(circularMeanPhase(beatFrames, periodFrames), periodFrames)
162
- : 0;
163
- return __assign({ bpm: Math.round(bpm), periodFrames: periodFrames,
164
- phaseFrame: phaseFrame }, (beatsSec.length > 0 ? { beatsSec: beatsSec } : {}));
165
- }
166
- catch (_b) {
205
+ });
206
+ return best;
207
+ };
208
+ /**
209
+ * Beat offset in [0, beat) seconds: the loudest peak is taken as a strong beat, and the offsets
210
+ * of peaks that agree with it (within `TEMPO_OFFSET_TOLERANCE_SEC`) are averaged.
211
+ */
212
+ var getBeatOffsetSec = function (data, partSize, sampleRate, bpm) {
213
+ var beatSec = 60 / bpm;
214
+ var shift = Math.round(beatSec * TEMPO_PEAK_LEFT_SHIFT_SHARE * sampleRate);
215
+ var peaks = partPeaks(data, partSize, function (v) { return v; }, shift)
216
+ .filter(function (p) { return p.volume > SILENCE_THRESHOLD; })
217
+ .sort(function (a, b) { return b.volume - a.volume; });
218
+ if (peaks.length === 0) {
219
+ return 0;
220
+ }
221
+ var refOffset = wrapIntoPeriod(peaks[0].position / sampleRate, beatSec);
222
+ var diffSum = 0;
223
+ var count = 0;
224
+ for (var _i = 0, peaks_1 = peaks; _i < peaks_1.length; _i++) {
225
+ var peak = peaks_1[_i];
226
+ var diff = circularDiff(peak.position / sampleRate, refOffset, beatSec);
227
+ if (Math.abs(diff) < TEMPO_OFFSET_TOLERANCE_SEC) {
228
+ diffSum += diff;
229
+ count++;
230
+ }
231
+ }
232
+ return wrapIntoPeriod(refOffset + diffSum / count, beatSec);
233
+ };
234
+ /**
235
+ * Estimates the track tempo and beat phase (after BeatDetect.js by Arthur Beaulieu):
236
+ * band-pass the audio around the kick, take the loudest sample of every half second,
237
+ * keep the loudest half of those peaks, and pick the most frequent tempo among the intervals
238
+ * between neighbouring peaks. The beat phase comes from the loudest peak and the peaks aligned with it.
239
+ */
240
+ exports.estimateTempo = function (samples, sampleRate, fps) {
241
+ if (!samples || samples.length === 0 || !(sampleRate > 0) || !(fps > 0)) {
167
242
  return null;
168
243
  }
244
+ var length = Math.min(samples.length, Math.max(1, Math.floor(sampleRate * exports.MAX_TEMPO_ANALYSIS_SECONDS)));
245
+ var lowPassed = biquadFilter(samples, length, sampleRate, 'lowpass', TEMPO_LOW_PASS_HZ, TEMPO_FILTER_Q_DB);
246
+ var data = biquadFilter(lowPassed, length, sampleRate, 'highpass', TEMPO_HIGH_PASS_HZ, TEMPO_FILTER_Q_DB);
247
+ var partSize = Math.max(1, Math.round(sampleRate * TEMPO_PART_SECONDS));
248
+ var bpm = getMostFrequentTempo(getLoudPeaks(data, partSize), sampleRate);
249
+ if (bpm === null) {
250
+ return null;
251
+ }
252
+ var beatSec = 60 / bpm;
253
+ var offsetSec = getBeatOffsetSec(data, partSize, sampleRate, bpm);
254
+ var durationSec = length / sampleRate;
255
+ var beatsSec = [];
256
+ for (var t = offsetSec; t < durationSec; t += beatSec) {
257
+ beatsSec.push(t);
258
+ }
259
+ var periodFrames = fps * beatSec;
260
+ return __assign({ bpm: bpm,
261
+ periodFrames: periodFrames, phaseFrame: wrapIntoPeriod(offsetSec * fps, periodFrames) }, (beatsSec.length > 0 ? { beatsSec: beatsSec } : {}));
169
262
  };
170
263
  exports.shiftTempoPhase = function (tempo, startFrame) {
171
264
  var period = tempo.periodFrames;
package/dist/beats.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"beats.js","sourceRoot":"","sources":["../src/beats.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"beats.js","sourceRoot":"","sources":["../src/beats.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-audio-visualizer",
3
- "version": "5.4.0",
3
+ "version": "5.4.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,7 +56,6 @@
56
56
  "bmp-js": "^0.1.0",
57
57
  "ffmpeg-static": "^5.2.0",
58
58
  "fft-js": "^0.0.12",
59
- "jimp": "^0.9.3",
60
- "music-tempo": "^1.0.3"
59
+ "jimp": "^0.9.3"
61
60
  }
62
61
  }
package/src/beats.ts CHANGED
@@ -1,5 +1,3 @@
1
- import MusicTempo from 'music-tempo';
2
-
3
1
  export interface BeatInfo {
4
2
  isBeat: boolean;
5
3
  intensity: number;
@@ -28,14 +26,26 @@ export interface TempoEstimate {
28
26
  beatsSec?: number[];
29
27
  }
30
28
 
31
- export const MIN_TEMPO_BPM = 70;
29
+ /** Detected tempo is folded (doubled/halved) into this range. */
30
+ export const MIN_TEMPO_BPM = 90;
32
31
  export const MAX_TEMPO_BPM = 180;
33
- export const PREFERRED_TEMPO_BPM_MIN = 90;
34
- export const PREFERRED_TEMPO_BPM_MAX = 160;
35
32
  export const DEFAULT_BASS_END_INDEX = 8;
36
- /** Longest PCM window passed to music-tempo (seconds), taken from the start of the audio. */
33
+ /** Longest PCM window used for tempo analysis (seconds), taken from the start of the audio. */
37
34
  export const MAX_TEMPO_ANALYSIS_SECONDS = 90;
38
- const TEMPO_TIME_STEP = 0.01;
35
+ /** Band-pass (low pass then high pass) that isolates kick impulses before peak picking. */
36
+ const TEMPO_LOW_PASS_HZ = 150;
37
+ const TEMPO_HIGH_PASS_HZ = 100;
38
+ /** Biquad Q in dB, as in the Web Audio BiquadFilterNode. */
39
+ const TEMPO_FILTER_Q_DB = 1;
40
+ /** Audio is split into parts of this length; each part contributes its loudest sample as a peak. */
41
+ const TEMPO_PART_SECONDS = 0.5;
42
+ /** Each peak is compared with this many following peaks to build intervals. */
43
+ const TEMPO_INTERVAL_NEIGHBOURS = 9;
44
+ /** Peaks are moved left by this share of a beat to land on the start of the hit, not its maximum. */
45
+ const TEMPO_PEAK_LEFT_SHIFT_SHARE = 0.05;
46
+ /** Peaks whose beat offset is within this many seconds of the loudest peak's offset set the phase. */
47
+ const TEMPO_OFFSET_TOLERANCE_SEC = 0.05;
48
+ const SILENCE_THRESHOLD = 1e-4;
39
49
 
40
50
  export const frameBassEnergy = (spectrum: number[], bassEndIndex = DEFAULT_BASS_END_INDEX): number => {
41
51
  const end = Math.min(bassEndIndex, spectrum.length);
@@ -98,29 +108,6 @@ export const createBeatDetector = (fps: number, options?: BeatDetectorOptions) =
98
108
  };
99
109
  };
100
110
 
101
- const foldTempoBpm = (bpm: number): number => {
102
- let folded = bpm;
103
- while (folded > MAX_TEMPO_BPM && folded / 2 >= MIN_TEMPO_BPM) {
104
- folded /= 2;
105
- }
106
- while (folded < MIN_TEMPO_BPM && folded * 2 <= MAX_TEMPO_BPM) {
107
- folded *= 2;
108
- }
109
- return folded;
110
- };
111
-
112
- const samplesToArray = (samples: ArrayLike<number>, maxLength: number): number[] => {
113
- const n = Math.min(samples.length, maxLength);
114
- if (Array.isArray(samples) && n === samples.length) {
115
- return samples;
116
- }
117
- const audioData = new Array<number>(n);
118
- for (let i = 0; i < n; i++) {
119
- audioData[i] = samples[i];
120
- }
121
- return audioData;
122
- };
123
-
124
111
  const wrapIntoPeriod = (value: number, period: number): number => {
125
112
  if (!(period > 0) || !isFinite(value)) {
126
113
  return 0;
@@ -150,58 +137,188 @@ const circularMeanPhase = (frames: number[], period: number): number => {
150
137
  return wrapIntoPeriod(phase, period);
151
138
  };
152
139
 
153
- export const estimateTempo = (
140
+ /** Signed distance from `value` to `reference` on a circle of length `period`, in [-period/2, period/2). */
141
+ const circularDiff = (value: number, reference: number, period: number): number =>
142
+ wrapIntoPeriod(value - reference + period / 2, period) - period / 2;
143
+
144
+ type BiquadType = 'lowpass' | 'highpass';
145
+
146
+ /** Biquad filter with the Web Audio BiquadFilterNode (RBJ cookbook) coefficients. */
147
+ const biquadFilter = (
154
148
  samples: ArrayLike<number>,
149
+ length: number,
155
150
  sampleRate: number,
156
- fps: number,
157
- ): TempoEstimate | null => {
158
- if (!samples || samples.length === 0 || !(sampleRate > 0) || !(fps > 0)) {
159
- return null;
151
+ type: BiquadType,
152
+ frequency: number,
153
+ qDb: number,
154
+ ): Float32Array => {
155
+ const w0 = 2 * Math.PI * Math.min(frequency, sampleRate / 2 - 1) / sampleRate;
156
+ const cosW0 = Math.cos(w0);
157
+ const alpha = Math.sin(w0) / (2 * Math.pow(10, qDb / 20));
158
+ const a0 = 1 + alpha;
159
+ const b1 = (type === 'lowpass' ? 1 - cosW0 : -(1 + cosW0)) / a0;
160
+ const b0 = (type === 'lowpass' ? b1 / 2 : -b1 / 2);
161
+ const b2 = b0;
162
+ const a1 = (-2 * cosW0) / a0;
163
+ const a2 = (1 - alpha) / a0;
164
+
165
+ const out = new Float32Array(length);
166
+ let x1 = 0;
167
+ let x2 = 0;
168
+ let y1 = 0;
169
+ let y2 = 0;
170
+ for (let i = 0; i < length; i++) {
171
+ const x = samples[i];
172
+ const y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
173
+ out[i] = y;
174
+ x2 = x1;
175
+ x1 = x;
176
+ y2 = y1;
177
+ y1 = y;
178
+ }
179
+ return out;
180
+ };
181
+
182
+ interface Peak {
183
+ position: number;
184
+ volume: number;
185
+ }
186
+
187
+ /** Loudest sample of each part (`getVolume` picks how a sample is measured). */
188
+ const partPeaks = (
189
+ data: Float32Array,
190
+ partSize: number,
191
+ getVolume: (sample: number) => number,
192
+ positionShift = 0,
193
+ ): Peak[] => {
194
+ const peaks: Peak[] = [];
195
+ for (let start = 0; start < data.length; start += partSize) {
196
+ const end = Math.min(start + partSize, data.length);
197
+ let max: Peak | null = null;
198
+ for (let j = start; j < end; j++) {
199
+ const volume = getVolume(data[j]);
200
+ if (!max || volume > max.volume) {
201
+ max = { position: j - positionShift, volume };
202
+ }
203
+ }
204
+ if (max) {
205
+ peaks.push(max);
206
+ }
207
+ }
208
+ return peaks;
209
+ };
210
+
211
+ /** Loudest half of the per-part peaks, in playback order. */
212
+ const getLoudPeaks = (data: Float32Array, partSize: number): Peak[] => {
213
+ const peaks = partPeaks(data, partSize, Math.abs)
214
+ .sort((a, b) => b.volume - a.volume);
215
+ return peaks
216
+ .slice(0, Math.floor(peaks.length / 2))
217
+ .filter(p => p.volume > SILENCE_THRESHOLD)
218
+ .sort((a, b) => a.position - b.position);
219
+ };
220
+
221
+ const foldTempoBpm = (bpm: number): number => {
222
+ let folded = bpm;
223
+ while (folded <= MIN_TEMPO_BPM) {
224
+ folded *= 2;
225
+ }
226
+ while (folded > MAX_TEMPO_BPM) {
227
+ folded /= 2;
160
228
  }
229
+ return folded;
230
+ };
161
231
 
162
- const maxSamples = Math.max(1, Math.floor(sampleRate * MAX_TEMPO_ANALYSIS_SECONDS));
163
- const audioData = samplesToArray(samples, maxSamples);
164
- const hopSize = Math.max(1, Math.round(sampleRate * TEMPO_TIME_STEP));
165
-
166
- try {
167
- const mt = new MusicTempo(audioData, {
168
- hopSize,
169
- timeStep: TEMPO_TIME_STEP,
170
- minBeatInterval: 60 / MAX_TEMPO_BPM,
171
- maxBeatInterval: 60 / MIN_TEMPO_BPM,
172
- });
173
- const beatInterval = Number(mt.beatInterval);
174
- let bpm = Number(mt.tempo);
175
- if (!(bpm > 0) && beatInterval > 0) {
176
- bpm = 60 / beatInterval;
232
+ /** Most frequent tempo among the intervals between each peak and its following neighbours. */
233
+ const getMostFrequentTempo = (peaks: Peak[], sampleRate: number): number | null => {
234
+ const counts = new Map<number, number>();
235
+ peaks.forEach((peak, index) => {
236
+ for (let i = 1; index + i < peaks.length && i <= TEMPO_INTERVAL_NEIGHBOURS; i++) {
237
+ const distance = peaks[index + i].position - peak.position;
238
+ if (distance <= 0) {
239
+ continue;
240
+ }
241
+ const tempo = Math.round(foldTempoBpm((60 * sampleRate) / distance));
242
+ counts.set(tempo, (counts.get(tempo) ?? 0) + 1);
177
243
  }
178
- if (!(bpm > 0)) {
179
- return null;
244
+ });
245
+ let best: number | null = null;
246
+ let bestCount = 0;
247
+ counts.forEach((count, tempo) => {
248
+ if (count > bestCount) {
249
+ best = tempo;
250
+ bestCount = count;
180
251
  }
252
+ });
253
+ return best;
254
+ };
181
255
 
182
- bpm = foldTempoBpm(bpm);
183
- if (bpm < MIN_TEMPO_BPM * 0.9 || bpm > MAX_TEMPO_BPM * 1.1) {
184
- return null;
256
+ /**
257
+ * Beat offset in [0, beat) seconds: the loudest peak is taken as a strong beat, and the offsets
258
+ * of peaks that agree with it (within `TEMPO_OFFSET_TOLERANCE_SEC`) are averaged.
259
+ */
260
+ const getBeatOffsetSec = (data: Float32Array, partSize: number, sampleRate: number, bpm: number): number => {
261
+ const beatSec = 60 / bpm;
262
+ const shift = Math.round(beatSec * TEMPO_PEAK_LEFT_SHIFT_SHARE * sampleRate);
263
+ const peaks = partPeaks(data, partSize, v => v, shift)
264
+ .filter(p => p.volume > SILENCE_THRESHOLD)
265
+ .sort((a, b) => b.volume - a.volume);
266
+ if (peaks.length === 0) {
267
+ return 0;
268
+ }
269
+ const refOffset = wrapIntoPeriod(peaks[0].position / sampleRate, beatSec);
270
+ let diffSum = 0;
271
+ let count = 0;
272
+ for (const peak of peaks) {
273
+ const diff = circularDiff(peak.position / sampleRate, refOffset, beatSec);
274
+ if (Math.abs(diff) < TEMPO_OFFSET_TOLERANCE_SEC) {
275
+ diffSum += diff;
276
+ count++;
185
277
  }
278
+ }
279
+ return wrapIntoPeriod(refOffset + diffSum / count, beatSec);
280
+ };
186
281
 
187
- const periodFrames = fps * 60 / bpm;
188
- const beatsSec = (mt.beats ?? [])
189
- .map(t => Number(t))
190
- .filter(t => t >= 0 && isFinite(t));
191
- const beatFrames = beatsSec.map(t => t * fps);
192
- const phaseFrame = beatFrames.length > 0
193
- ? wrapIntoPeriod(circularMeanPhase(beatFrames, periodFrames), periodFrames)
194
- : 0;
282
+ /**
283
+ * Estimates the track tempo and beat phase (after BeatDetect.js by Arthur Beaulieu):
284
+ * band-pass the audio around the kick, take the loudest sample of every half second,
285
+ * keep the loudest half of those peaks, and pick the most frequent tempo among the intervals
286
+ * between neighbouring peaks. The beat phase comes from the loudest peak and the peaks aligned with it.
287
+ */
288
+ export const estimateTempo = (
289
+ samples: ArrayLike<number>,
290
+ sampleRate: number,
291
+ fps: number,
292
+ ): TempoEstimate | null => {
293
+ if (!samples || samples.length === 0 || !(sampleRate > 0) || !(fps > 0)) {
294
+ return null;
295
+ }
195
296
 
196
- return {
197
- bpm: Math.round(bpm),
198
- periodFrames,
199
- phaseFrame,
200
- ...(beatsSec.length > 0 ? { beatsSec } : {}),
201
- };
202
- } catch {
297
+ const length = Math.min(samples.length, Math.max(1, Math.floor(sampleRate * MAX_TEMPO_ANALYSIS_SECONDS)));
298
+ const lowPassed = biquadFilter(samples, length, sampleRate, 'lowpass', TEMPO_LOW_PASS_HZ, TEMPO_FILTER_Q_DB);
299
+ const data = biquadFilter(lowPassed, length, sampleRate, 'highpass', TEMPO_HIGH_PASS_HZ, TEMPO_FILTER_Q_DB);
300
+ const partSize = Math.max(1, Math.round(sampleRate * TEMPO_PART_SECONDS));
301
+
302
+ const bpm = getMostFrequentTempo(getLoudPeaks(data, partSize), sampleRate);
303
+ if (bpm === null) {
203
304
  return null;
204
305
  }
306
+
307
+ const beatSec = 60 / bpm;
308
+ const offsetSec = getBeatOffsetSec(data, partSize, sampleRate, bpm);
309
+ const durationSec = length / sampleRate;
310
+ const beatsSec: number[] = [];
311
+ for (let t = offsetSec; t < durationSec; t += beatSec) {
312
+ beatsSec.push(t);
313
+ }
314
+
315
+ const periodFrames = fps * beatSec;
316
+ return {
317
+ bpm,
318
+ periodFrames,
319
+ phaseFrame: wrapIntoPeriod(offsetSec * fps, periodFrames),
320
+ ...(beatsSec.length > 0 ? { beatsSec } : {}),
321
+ };
205
322
  };
206
323
 
207
324
  export const shiftTempoPhase = (
package/src/typings.d.ts CHANGED
@@ -4,26 +4,4 @@ declare module 'fft-js' {
4
4
  fftFreq(phasors: [number, number][], sampleRate: number): number[];
5
5
  fftMag(phasors: [number, number][]): number[];
6
6
  };
7
- }
8
-
9
- declare module 'music-tempo' {
10
- export interface MusicTempoParams {
11
- bufferSize?: number;
12
- hopSize?: number;
13
- timeStep?: number;
14
- decayRate?: number;
15
- peakFindingWindow?: number;
16
- meanWndMultiplier?: number;
17
- peakThreshold?: number;
18
- minBeatInterval?: number;
19
- maxBeatInterval?: number;
20
- expiryTime?: number;
21
- }
22
-
23
- export default class MusicTempo {
24
- constructor(audioData: number[] | Float32Array, params?: MusicTempoParams);
25
- tempo: string | number;
26
- beats: number[];
27
- beatInterval: number;
28
- }
29
7
  }