diff --git a/index.html b/index.html
index 6d91859..5f304dd 100644
--- a/index.html
+++ b/index.html
@@ -4352,6 +4352,26 @@
+
+
+
diff --git a/js/settings.js b/js/settings.js
index ac17f46..d2b7f62 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -1383,6 +1383,102 @@ export async function initializeSettings(scrobbler, player, api, ui) {
});
});
+ // Legacy EQ Import / Export
+ const GEQ_FREQUENCIES = [25, 40, 63, 100, 160, 250, 400, 630, 1000, 1600, 2500, 4000, 6300, 10000, 16000, 20000];
+ const legacyGeqExportBtn = document.getElementById('legacy-geq-export-btn');
+ const legacyGeqImportBtn = document.getElementById('legacy-geq-import-btn');
+ const legacyGeqImportFile = document.getElementById('legacy-geq-import-file');
+
+ if (legacyGeqExportBtn) {
+ legacyGeqExportBtn.addEventListener('click', () => {
+ const lines = [`Preamp: ${geqPreamp.toFixed(1)} dB`];
+ GEQ_FREQUENCIES.forEach((freq, i) => {
+ lines.push(`Filter ${i + 1}: ON PK Fc ${freq} Hz Gain ${geqGains[i].toFixed(1)} dB Q 1.41`);
+ });
+ const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'legacy-eq.txt';
+ a.click();
+ URL.revokeObjectURL(url);
+ });
+ }
+
+ if (legacyGeqImportBtn && legacyGeqImportFile) {
+ legacyGeqImportBtn.addEventListener('click', () => legacyGeqImportFile.click());
+ legacyGeqImportFile.addEventListener('change', (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const text = event.target.result;
+ const lines = text.split('\n');
+ let preamp = 0;
+ const importedPoints = [];
+
+ for (const line of lines) {
+ const preampMatch = line.match(/Preamp:\s*([-\d.]+)\s*dB/i);
+ if (preampMatch) {
+ preamp = parseFloat(preampMatch[1]);
+ continue;
+ }
+ // EqualizerAPO format: Filter N: ON PK Fc XXXX Hz Gain X.X dB Q X.XX
+ const filterMatch = line.match(
+ /Filter\s+\d+:\s*ON\s+\w+\s+Fc\s+([\d.]+)\s*Hz\s+Gain\s+([-\d.]+)\s*dB/i
+ );
+ if (filterMatch) {
+ importedPoints.push({ freq: parseFloat(filterMatch[1]), gain: parseFloat(filterMatch[2]) });
+ continue;
+ }
+ // Simple two-column format: freq gain (whitespace/tab/comma separated)
+ const simpleMatch = line.trim().match(/^([\d.]+)[,\s\t]+([-\d.]+)/);
+ if (simpleMatch) {
+ importedPoints.push({ freq: parseFloat(simpleMatch[1]), gain: parseFloat(simpleMatch[2]) });
+ }
+ }
+
+ if (importedPoints.length === 0) return;
+
+ // Sort by frequency
+ importedPoints.sort((a, b) => a.freq - b.freq);
+
+ // Map imported points to the 16 GEQ bands using nearest-frequency matching
+ const newGains = GEQ_FREQUENCIES.map((targetFreq) => {
+ // Find the closest imported point by log-frequency distance
+ let closest = importedPoints[0];
+ let minDist = Math.abs(Math.log10(targetFreq) - Math.log10(closest.freq));
+ for (let j = 1; j < importedPoints.length; j++) {
+ const dist = Math.abs(Math.log10(targetFreq) - Math.log10(importedPoints[j].freq));
+ if (dist < minDist) {
+ minDist = dist;
+ closest = importedPoints[j];
+ }
+ }
+ // Clamp to slider range
+ return Math.max(parseFloat(geqRange.min), Math.min(parseFloat(geqRange.max), closest.gain));
+ });
+
+ geqGains = newGains;
+ geqPreamp = Math.max(-20, Math.min(20, preamp));
+ equalizerSettings.setGraphicEqGains(geqGains);
+ equalizerSettings.setGraphicEqPreamp(geqPreamp);
+ audioContextManager.setGraphicEqAllGains(geqGains);
+ audioContextManager.setGraphicEqPreamp(geqPreamp);
+ geqSyncAllSliders();
+ geqPreampSliders.forEach((s) => (s.value = geqPreamp));
+ geqPreampValues.forEach((v) => (v.textContent = `${geqPreamp.toFixed(1)} dB`));
+ geqPresetSelects.forEach((s) => (s.value = ''));
+ } catch (err) {
+ console.error('[Legacy GEQ Import] Failed:', err);
+ }
+ };
+ reader.readAsText(file);
+ e.target.value = '';
+ });
+ }
+
// ========================================
// Precision AutoEQ - Redesigned Equalizer
// ========================================