summaryrefslogtreecommitdiff
path: root/tools/mq_editor/mq_extract.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mq_editor/mq_extract.js')
-rw-r--r--tools/mq_editor/mq_extract.js59
1 files changed, 52 insertions, 7 deletions
diff --git a/tools/mq_editor/mq_extract.js b/tools/mq_editor/mq_extract.js
index 62b275c..c7ead4d 100644
--- a/tools/mq_editor/mq_extract.js
+++ b/tools/mq_editor/mq_extract.js
@@ -97,7 +97,12 @@ function detectPeaks(frame, fftSize, sampleRate, thresholdDB) {
function trackPartials(frames, sampleRate) {
const partials = [];
const activePartials = [];
- const trackingThreshold = 50; // Hz
+ const candidatePartials = []; // Pre-birth candidates
+ const trackingThresholdRatio = 0.05; // 5% frequency tolerance
+ const minTrackingHz = 20; // Minimum 20 Hz
+ const birthPersistence = 3; // Require 3 consecutive frames to birth
+ const deathAge = 5; // Allow 5 frame gap before death
+ const minPartialLength = 10; // Minimum 10 frames for valid partial
for (const frame of frames) {
const matched = new Set();
@@ -105,6 +110,7 @@ function trackPartials(frames, sampleRate) {
// Match peaks to existing partials
for (const partial of activePartials) {
const lastFreq = partial.freqs[partial.freqs.length - 1];
+ const threshold = Math.max(lastFreq * trackingThresholdRatio, minTrackingHz);
let bestPeak = null;
let bestDist = Infinity;
@@ -115,7 +121,7 @@ function trackPartials(frames, sampleRate) {
const peak = frame.peaks[i];
const dist = Math.abs(peak.freq - lastFreq);
- if (dist < trackingThreshold && dist < bestDist) {
+ if (dist < threshold && dist < bestDist) {
bestPeak = peak;
bestDist = dist;
partial.matchIdx = i;
@@ -135,12 +141,51 @@ function trackPartials(frames, sampleRate) {
}
}
- // Birth new partials from unmatched peaks
+ // Update candidate partials (pre-birth)
+ for (let i = candidatePartials.length - 1; i >= 0; --i) {
+ const candidate = candidatePartials[i];
+ const lastFreq = candidate.freqs[candidate.freqs.length - 1];
+ const threshold = Math.max(lastFreq * trackingThresholdRatio, minTrackingHz);
+
+ let bestPeak = null;
+ let bestDist = Infinity;
+
+ for (let i = 0; i < frame.peaks.length; ++i) {
+ if (matched.has(i)) continue;
+
+ const peak = frame.peaks[i];
+ const dist = Math.abs(peak.freq - lastFreq);
+
+ if (dist < threshold && dist < bestDist) {
+ bestPeak = peak;
+ bestDist = dist;
+ candidate.matchIdx = i;
+ }
+ }
+
+ if (bestPeak) {
+ candidate.times.push(frame.time);
+ candidate.freqs.push(bestPeak.freq);
+ candidate.amps.push(bestPeak.amp);
+ matched.add(candidate.matchIdx);
+
+ // Birth if persistent enough
+ if (candidate.times.length >= birthPersistence) {
+ activePartials.push(candidate);
+ candidatePartials.splice(i, 1);
+ }
+ } else {
+ // Candidate died, remove
+ candidatePartials.splice(i, 1);
+ }
+ }
+
+ // Create new candidate partials from unmatched peaks
for (let i = 0; i < frame.peaks.length; ++i) {
if (matched.has(i)) continue;
const peak = frame.peaks[i];
- activePartials.push({
+ candidatePartials.push({
times: [frame.time],
freqs: [peak.freq],
amps: [peak.amp],
@@ -151,9 +196,9 @@ function trackPartials(frames, sampleRate) {
// Death old partials
for (let i = activePartials.length - 1; i >= 0; --i) {
- if (activePartials[i].age > 2) {
+ if (activePartials[i].age > deathAge) {
// Move to finished if long enough
- if (activePartials[i].times.length >= 4) {
+ if (activePartials[i].times.length >= minPartialLength) {
partials.push(activePartials[i]);
}
activePartials.splice(i, 1);
@@ -163,7 +208,7 @@ function trackPartials(frames, sampleRate) {
// Finish remaining active partials
for (const partial of activePartials) {
- if (partial.times.length >= 4) {
+ if (partial.times.length >= minPartialLength) {
partials.push(partial);
}
}