summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/mq_editor/README.md3
-rw-r--r--tools/mq_editor/index.html16
-rw-r--r--tools/mq_editor/mq_extract.js11
3 files changed, 22 insertions, 8 deletions
diff --git a/tools/mq_editor/README.md b/tools/mq_editor/README.md
index 141963a..0ef2e72 100644
--- a/tools/mq_editor/README.md
+++ b/tools/mq_editor/README.md
@@ -27,6 +27,7 @@ open tools/mq_editor/index.html
- **Hop Size:** 64–1024 samples (default 256)
- **Threshold:** dB floor for peak detection (default −60 dB)
+- **f·Power:** checkbox — weight spectrum by frequency (`f·FFT_Power(f)`) before peak detection, accentuating high-frequency peaks
- **Keep %:** slider to limit how many partials are shown/synthesized
## Keyboard Shortcuts
@@ -67,7 +68,7 @@ open tools/mq_editor/index.html
## Algorithm
1. **STFT:** Overlapping Hann windows, radix-2 FFT
-2. **Peak Detection:** Local maxima above threshold + parabolic interpolation
+2. **Peak Detection:** Local maxima above threshold + parabolic interpolation; optional `f·Power(f)` frequency weighting to accentuate high-frequency peaks
3. **Forward Tracking:** Birth/death/continuation with frequency-dependent tolerance, candidate persistence
4. **Backward Expansion:** Second pass extends each partial leftward to recover onset frames
5. **Bezier Fitting:** Cubic curves with control points at t/3 and 2t/3
diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html
index f6d052b..aab603b 100644
--- a/tools/mq_editor/index.html
+++ b/tools/mq_editor/index.html
@@ -261,6 +261,10 @@
<label>Threshold (dB):</label>
<input type="number" id="threshold" value="-20" step="any">
+ <label style="margin-left:16px;" title="Weight spectrum by frequency before peak detection (f * FFT_Power(f)), accentuates high-frequency peaks">
+ <input type="checkbox" id="freqWeight"> f·Power
+ </label>
+
<label style="margin-left:16px;">Keep:</label>
<input type="range" id="keepPct" min="1" max="100" value="100" style="width:100px; vertical-align:middle;">
<span id="keepPctLabel" style="margin-left:4px;">100%</span>
@@ -366,6 +370,7 @@
const hopSize = document.getElementById('hopSize');
const threshold = document.getElementById('threshold');
+ const freqWeightCb = document.getElementById('freqWeight');
const keepPct = document.getElementById('keepPct');
const keepPctLabel = document.getElementById('keepPctLabel');
const fftSize = 1024; // Fixed
@@ -483,6 +488,7 @@
fftSize: fftSize,
hopSize: parseInt(hopSize.value),
threshold: parseFloat(threshold.value),
+ freqWeight: freqWeightCb.checked,
sampleRate: audioBuffer.sampleRate
};
@@ -501,9 +507,7 @@
setStatus(`Extracted ${result.partials.length} partials`, 'info');
viewer.setPartials(result.partials);
viewer.setKeepCount(getKeepCount());
- // Refresh panels: re-select if index still valid, else clear
- const prevSel = viewer.selectedPartial;
- viewer.selectPartial(prevSel >= 0 && prevSel < result.partials.length ? prevSel : -1);
+ viewer.selectPartial(-1);
} catch (err) {
setStatus('Extraction error: ' + err.message, 'error');
@@ -531,6 +535,8 @@
p.replicas.spread_below = spread_below;
}
if (viewer) viewer.render();
+ const sel = viewer ? viewer.selectedPartial : -1;
+ if (sel >= 0) editor.onPartialSelect(sel);
setStatus(`Auto-spread applied to ${extractedPartials.length} partials`, 'info');
});
@@ -538,6 +544,10 @@
if (stftCache) runExtraction();
});
+ freqWeightCb.addEventListener('change', () => {
+ if (stftCache) runExtraction();
+ });
+
function playAudioBuffer(buffer, statusMsg) {
const startTime = audioContext.currentTime;
currentSource = audioContext.createBufferSource();
diff --git a/tools/mq_editor/mq_extract.js b/tools/mq_editor/mq_extract.js
index d29cfbc..c03e869 100644
--- a/tools/mq_editor/mq_extract.js
+++ b/tools/mq_editor/mq_extract.js
@@ -3,14 +3,14 @@
// Extract partials from audio buffer
function extractPartials(params, stftCache) {
- const {fftSize, threshold, sampleRate} = params;
+ const {fftSize, threshold, sampleRate, freqWeight} = params;
const numFrames = stftCache.getNumFrames();
const frames = [];
for (let i = 0; i < numFrames; ++i) {
const cachedFrame = stftCache.getFrameAtIndex(i);
const squaredAmp = stftCache.getSquaredAmplitude(cachedFrame.time);
- const peaks = detectPeaks(squaredAmp, fftSize, sampleRate, threshold);
+ const peaks = detectPeaks(squaredAmp, fftSize, sampleRate, threshold, freqWeight);
frames.push({time: cachedFrame.time, peaks});
}
@@ -29,10 +29,13 @@ function extractPartials(params, stftCache) {
// Detect spectral peaks via local maxima + parabolic interpolation
// squaredAmp: pre-computed re*re+im*im per bin
-function detectPeaks(squaredAmp, fftSize, sampleRate, thresholdDB) {
+// freqWeight: if true, weight by f before peak detection (f * Power(f))
+function detectPeaks(squaredAmp, fftSize, sampleRate, thresholdDB, freqWeight) {
const mag = new Float32Array(fftSize / 2);
+ const binHz = sampleRate / fftSize;
for (let i = 0; i < fftSize / 2; ++i) {
- mag[i] = 10 * Math.log10(Math.max(squaredAmp[i], 1e-20));
+ const w = freqWeight ? (i * binHz) : 1.0;
+ mag[i] = 10 * Math.log10(Math.max(squaredAmp[i] * w, 1e-20));
}
const peaks = [];