style: auto-fix linting issues
This commit is contained in:
parent
d4d1fe8494
commit
77f9e10fdc
4 changed files with 164 additions and 59 deletions
|
|
@ -2,7 +2,6 @@
|
||||||
// Target Curves & Data Parser - Ported from Seap Engine
|
// Target Curves & Data Parser - Ported from Seap Engine
|
||||||
// Contains target frequency response curves and raw data parser
|
// Contains target frequency response curves and raw data parser
|
||||||
|
|
||||||
|
|
||||||
// Raw content synchronized with features/autoeq/Targets/ directory
|
// Raw content synchronized with features/autoeq/Targets/ directory
|
||||||
|
|
||||||
const RAW_HARMAN_OE_2018 = `
|
const RAW_HARMAN_OE_2018 = `
|
||||||
|
|
@ -2713,7 +2712,6 @@ const RAW_HIFI_ENDGAME_2026_MKII = `
|
||||||
20000.00 72.378
|
20000.00 72.378
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const RAW_PEQDB_ULTRA = `
|
const RAW_PEQDB_ULTRA = `
|
||||||
20.00 83.153
|
20.00 83.153
|
||||||
20.36 83.149
|
20.36 83.149
|
||||||
|
|
@ -3487,7 +3485,6 @@ const RAW_PEQDB_DIAMOND_BETA = `
|
||||||
20000.00 72.028
|
20000.00 72.028
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const RAW_SEAP = `
|
const RAW_SEAP = `
|
||||||
20.00 82.982
|
20.00 82.982
|
||||||
20.36 82.975
|
20.36 82.975
|
||||||
|
|
@ -4301,7 +4298,6 @@ const RAW_FLAT_LINE = `
|
||||||
20000.00 75.000
|
20000.00 75.000
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
// --- PARSER ---
|
// --- PARSER ---
|
||||||
/**
|
/**
|
||||||
* Parse raw frequency/gain text data into [{freq, gain}] arrays
|
* Parse raw frequency/gain text data into [{freq, gain}] arrays
|
||||||
|
|
@ -4331,15 +4327,17 @@ function parseRawData(raw) {
|
||||||
|
|
||||||
const hasHeader = /[a-zA-Z]/.test(firstLine);
|
const hasHeader = /[a-zA-Z]/.test(firstLine);
|
||||||
if (hasHeader) {
|
if (hasHeader) {
|
||||||
const headers = firstLine.split(delimiter).map(h => h.trim().toLowerCase().replace(/['"]+/g, ''));
|
const headers = firstLine.split(delimiter).map((h) => h.trim().toLowerCase().replace(/['"]+/g, ''));
|
||||||
const fIdx = headers.findIndex(h => h.includes('freq') || h === 'f');
|
const fIdx = headers.findIndex((h) => h.includes('freq') || h === 'f');
|
||||||
if (fIdx > -1) freqIdx = fIdx;
|
if (fIdx > -1) freqIdx = fIdx;
|
||||||
|
|
||||||
const rIdx = headers.findIndex(h => h === 'raw');
|
const rIdx = headers.findIndex((h) => h === 'raw');
|
||||||
if (rIdx > -1) {
|
if (rIdx > -1) {
|
||||||
gainIdx = rIdx;
|
gainIdx = rIdx;
|
||||||
} else {
|
} else {
|
||||||
const splIdx = headers.findIndex(h => h.includes('spl') || h.includes('gain') || h.includes('db') || h.includes('mag'));
|
const splIdx = headers.findIndex(
|
||||||
|
(h) => h.includes('spl') || h.includes('gain') || h.includes('db') || h.includes('mag')
|
||||||
|
);
|
||||||
if (splIdx > -1 && splIdx !== freqIdx) gainIdx = splIdx;
|
if (splIdx > -1 && splIdx !== freqIdx) gainIdx = splIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,42 +21,55 @@ const DB_DIVISOR = 40;
|
||||||
function calculateBiquadResponse(f, band, sr = DEFAULT_SR) {
|
function calculateBiquadResponse(f, band, sr = DEFAULT_SR) {
|
||||||
if (!band.enabled) return 0;
|
if (!band.enabled) return 0;
|
||||||
if (!band.type || band.type.length === 0) return 0;
|
if (!band.type || band.type.length === 0) return 0;
|
||||||
const w = 2 * PI * band.freq / sr;
|
const w = (2 * PI * band.freq) / sr;
|
||||||
const p = 2 * PI * f / sr;
|
const p = (2 * PI * f) / sr;
|
||||||
const s = Math.sin(w) / (2 * band.q);
|
const s = Math.sin(w) / (2 * band.q);
|
||||||
const A = Math.pow(DB_BASE, band.gain / DB_DIVISOR);
|
const A = Math.pow(DB_BASE, band.gain / DB_DIVISOR);
|
||||||
const c = Math.cos(w);
|
const c = Math.cos(w);
|
||||||
let b0 = 0, b1 = 0, b2 = 0, a0 = 0, a1 = 0, a2 = 0;
|
let b0 = 0,
|
||||||
|
b1 = 0,
|
||||||
|
b2 = 0,
|
||||||
|
a0 = 0,
|
||||||
|
a1 = 0,
|
||||||
|
a2 = 0;
|
||||||
|
|
||||||
const t = band.type[0];
|
const t = band.type[0];
|
||||||
|
|
||||||
if (t === 'p') {
|
if (t === 'p') {
|
||||||
b0 = 1 + s * A; b1 = -2 * c; b2 = 1 - s * A;
|
b0 = 1 + s * A;
|
||||||
a0 = 1 + s / A; a1 = -2 * c; a2 = 1 - s / A;
|
b1 = -2 * c;
|
||||||
|
b2 = 1 - s * A;
|
||||||
|
a0 = 1 + s / A;
|
||||||
|
a1 = -2 * c;
|
||||||
|
a2 = 1 - s / A;
|
||||||
} else if (t === 'l') {
|
} else if (t === 'l') {
|
||||||
const sq = 2 * Math.sqrt(A) * s;
|
const sq = 2 * Math.sqrt(A) * s;
|
||||||
b0 = A * ((A + 1) - (A - 1) * c + sq);
|
b0 = A * (A + 1 - (A - 1) * c + sq);
|
||||||
b1 = 2 * A * ((A - 1) - (A + 1) * c);
|
b1 = 2 * A * (A - 1 - (A + 1) * c);
|
||||||
b2 = A * ((A + 1) - (A - 1) * c - sq);
|
b2 = A * (A + 1 - (A - 1) * c - sq);
|
||||||
a0 = (A + 1) + (A - 1) * c + sq;
|
a0 = A + 1 + (A - 1) * c + sq;
|
||||||
a1 = -2 * ((A - 1) + (A + 1) * c);
|
a1 = -2 * (A - 1 + (A + 1) * c);
|
||||||
a2 = (A + 1) + (A - 1) * c - sq;
|
a2 = A + 1 + (A - 1) * c - sq;
|
||||||
} else if (t === 'h') {
|
} else if (t === 'h') {
|
||||||
const sq = 2 * Math.sqrt(A) * s;
|
const sq = 2 * Math.sqrt(A) * s;
|
||||||
b0 = A * ((A + 1) + (A - 1) * c + sq);
|
b0 = A * (A + 1 + (A - 1) * c + sq);
|
||||||
b1 = -2 * A * ((A - 1) + (A + 1) * c);
|
b1 = -2 * A * (A - 1 + (A + 1) * c);
|
||||||
b2 = A * ((A + 1) + (A - 1) * c - sq);
|
b2 = A * (A + 1 + (A - 1) * c - sq);
|
||||||
a0 = (A + 1) - (A - 1) * c + sq;
|
a0 = A + 1 - (A - 1) * c + sq;
|
||||||
a1 = 2 * ((A - 1) - (A + 1) * c);
|
a1 = 2 * (A - 1 - (A + 1) * c);
|
||||||
a2 = (A + 1) - (A - 1) * c - sq;
|
a2 = A + 1 - (A - 1) * c - sq;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _a0 = 1 / a0;
|
const _a0 = 1 / a0;
|
||||||
const b0n = b0 * _a0, b1n = b1 * _a0, b2n = b2 * _a0;
|
const b0n = b0 * _a0,
|
||||||
const a1n = a1 * _a0, a2n = a2 * _a0;
|
b1n = b1 * _a0,
|
||||||
const cp = Math.cos(p), c2p = Math.cos(2 * p);
|
b2n = b2 * _a0;
|
||||||
|
const a1n = a1 * _a0,
|
||||||
|
a2n = a2 * _a0;
|
||||||
|
const cp = Math.cos(p),
|
||||||
|
c2p = Math.cos(2 * p);
|
||||||
const n = b0n * b0n + b1n * b1n + b2n * b2n + 2 * (b0n * b1n + b1n * b2n) * cp + 2 * b0n * b2n * c2p;
|
const n = b0n * b0n + b1n * b1n + b2n * b2n + 2 * (b0n * b1n + b1n * b2n) * cp + 2 * b0n * b2n * c2p;
|
||||||
const d = 1 + a1n * a1n + a2n * a2n + 2 * (a1n + a1n * a2n) * cp + 2 * a2n * c2p;
|
const d = 1 + a1n * a1n + a2n * a2n + 2 * (a1n + a1n * a2n) * cp + 2 * a2n * c2p;
|
||||||
return 10 * Math.log10(n / d);
|
return 10 * Math.log10(n / d);
|
||||||
|
|
@ -74,7 +87,10 @@ function interpolate(freq, data) {
|
||||||
if (freq >= data[data.length - 1].freq) return data[data.length - 1].gain;
|
if (freq >= data[data.length - 1].freq) return data[data.length - 1].gain;
|
||||||
for (let i = 0; i < data.length - 1; i++) {
|
for (let i = 0; i < data.length - 1; i++) {
|
||||||
if (freq >= data[i].freq && freq <= data[i + 1].freq) {
|
if (freq >= data[i].freq && freq <= data[i + 1].freq) {
|
||||||
return data[i].gain + (freq - data[i].freq) / (data[i + 1].freq - data[i].freq) * (data[i + 1].gain - data[i].gain);
|
return (
|
||||||
|
data[i].gain +
|
||||||
|
((freq - data[i].freq) / (data[i + 1].freq - data[i].freq)) * (data[i + 1].gain - data[i].gain)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -86,7 +102,8 @@ function interpolate(freq, data) {
|
||||||
* @returns {number} Average gain in midrange
|
* @returns {number} Average gain in midrange
|
||||||
*/
|
*/
|
||||||
function getNormalizationOffset(data) {
|
function getNormalizationOffset(data) {
|
||||||
let sum = 0, count = 0;
|
let sum = 0,
|
||||||
|
count = 0;
|
||||||
for (const p of data) {
|
for (const p of data) {
|
||||||
if (p.freq >= 250 && p.freq <= 2500) {
|
if (p.freq >= 250 && p.freq <= 2500) {
|
||||||
sum += p.gain;
|
sum += p.gain;
|
||||||
|
|
@ -108,18 +125,29 @@ function getNormalizationOffset(data) {
|
||||||
* @param {number} maxQ - Maximum Q factor
|
* @param {number} maxQ - Maximum Q factor
|
||||||
* @returns {Array<{id: number, type: string, freq: number, gain: number, q: number, enabled: boolean}>}
|
* @returns {Array<{id: number, type: string, freq: number, gain: number, q: number, enabled: boolean}>}
|
||||||
*/
|
*/
|
||||||
function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, minFreq = 20, maxQ = 5.0, sampleRate = DEFAULT_SR) {
|
function runAutoEqAlgorithm(
|
||||||
|
measurement,
|
||||||
|
target,
|
||||||
|
bandCount,
|
||||||
|
maxFreq = 16000,
|
||||||
|
minFreq = 20,
|
||||||
|
maxQ = 5.0,
|
||||||
|
sampleRate = DEFAULT_SR
|
||||||
|
) {
|
||||||
if (minFreq > maxFreq) return [];
|
if (minFreq > maxFreq) return [];
|
||||||
const off = getNormalizationOffset(target) - getNormalizationOffset(measurement);
|
const off = getNormalizationOffset(target) - getNormalizationOffset(measurement);
|
||||||
let err = measurement.map(p => ({ freq: p.freq, gain: (p.gain + off) - interpolate(p.freq, target) }));
|
let err = measurement.map((p) => ({ freq: p.freq, gain: p.gain + off - interpolate(p.freq, target) }));
|
||||||
|
|
||||||
const hasInRangePoints = err.some(p => p.freq >= minFreq && p.freq <= maxFreq);
|
const hasInRangePoints = err.some((p) => p.freq >= minFreq && p.freq <= maxFreq);
|
||||||
if (!hasInRangePoints) return [];
|
if (!hasInRangePoints) return [];
|
||||||
|
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
for (let i = 0; i < bandCount; i++) {
|
for (let i = 0; i < bandCount; i++) {
|
||||||
let maxDev = 0, maxWeightedDev = 0, peakFreq = 1000, peakIdx = 0;
|
let maxDev = 0,
|
||||||
|
maxWeightedDev = 0,
|
||||||
|
peakFreq = 1000,
|
||||||
|
peakIdx = 0;
|
||||||
|
|
||||||
// Scan for maximum weighted error
|
// Scan for maximum weighted error
|
||||||
for (let j = 0; j < err.length; j++) {
|
for (let j = 0; j < err.length; j++) {
|
||||||
|
|
@ -158,8 +186,10 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min
|
||||||
if (Math.abs(gain) < 0.2) break;
|
if (Math.abs(gain) < 0.2) break;
|
||||||
|
|
||||||
// Q factor calculation from error bandwidth (half-gain points)
|
// Q factor calculation from error bandwidth (half-gain points)
|
||||||
let upperFreq = peakFreq, lowerFreq = peakFreq;
|
let upperFreq = peakFreq,
|
||||||
let foundLower = false, foundUpper = false;
|
lowerFreq = peakFreq;
|
||||||
|
let foundLower = false,
|
||||||
|
foundUpper = false;
|
||||||
const thresholdError = maxDev / 2;
|
const thresholdError = maxDev / 2;
|
||||||
for (let k = peakIdx; k >= 0; k--) {
|
for (let k = peakIdx; k >= 0; k--) {
|
||||||
if (Math.abs(err[k].gain) < Math.abs(thresholdError)) {
|
if (Math.abs(err[k].gain) < Math.abs(thresholdError)) {
|
||||||
|
|
@ -179,9 +209,9 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min
|
||||||
// If half-gain boundary not found on one side, mirror the other side
|
// If half-gain boundary not found on one side, mirror the other side
|
||||||
// to avoid degenerate bandwidth = 0 producing extremely narrow filters
|
// to avoid degenerate bandwidth = 0 producing extremely narrow filters
|
||||||
if (!foundLower && foundUpper) {
|
if (!foundLower && foundUpper) {
|
||||||
lowerFreq = peakFreq * peakFreq / upperFreq;
|
lowerFreq = (peakFreq * peakFreq) / upperFreq;
|
||||||
} else if (!foundUpper && foundLower) {
|
} else if (!foundUpper && foundLower) {
|
||||||
upperFreq = peakFreq * peakFreq / lowerFreq;
|
upperFreq = (peakFreq * peakFreq) / lowerFreq;
|
||||||
} else if (!foundLower && !foundUpper) {
|
} else if (!foundLower && !foundUpper) {
|
||||||
// Neither boundary found — use 1 octave default
|
// Neither boundary found — use 1 octave default
|
||||||
lowerFreq = peakFreq / Math.SQRT2;
|
lowerFreq = peakFreq / Math.SQRT2;
|
||||||
|
|
@ -212,7 +242,7 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min
|
||||||
out.push(newBand);
|
out.push(newBand);
|
||||||
|
|
||||||
// Update error curve by applying the new band's response
|
// Update error curve by applying the new band's response
|
||||||
err = err.map(p => ({ ...p, gain: p.gain + calculateBiquadResponse(p.freq, newBand, sampleRate) }));
|
err = err.map((p) => ({ ...p, gain: p.gain + calculateBiquadResponse(p.freq, newBand, sampleRate) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.sort((a, b) => a.freq - b.freq).map((b, i) => ({ ...b, id: i }));
|
return out.sort((a, b) => a.freq - b.freq).map((b, i) => ({ ...b, id: i }));
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,83 @@ const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
// 5 most popular headphones — pre-loaded as defaults and shown in the headphone select
|
// 5 most popular headphones — pre-loaded as defaults and shown in the headphone select
|
||||||
// All measured on Rtings B&K 5128 rig for consistency
|
// All measured on Rtings B&K 5128 rig for consistency
|
||||||
const POPULAR_HEADPHONES = [
|
const POPULAR_HEADPHONES = [
|
||||||
{ name: 'Sony WH-1000XM5 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sony WH-1000XM5', fileName: 'Sony WH-1000XM5.csv' },
|
{
|
||||||
{ name: 'Apple AirPods Pro2 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Apple AirPods Pro2', fileName: 'Apple AirPods Pro2.csv' },
|
name: 'Sony WH-1000XM5 (Rtings)',
|
||||||
{ name: 'Sony WF-1000XM5 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' },
|
type: 'over-ear',
|
||||||
{ name: 'Samsung Galaxy Buds3 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' },
|
path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sony WH-1000XM5',
|
||||||
{ name: 'Sennheiser HD 600 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' },
|
fileName: 'Sony WH-1000XM5.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Apple AirPods Pro2 (Rtings)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Rtings/Bruel & Kjaer 5128 in-ear/Apple AirPods Pro2',
|
||||||
|
fileName: 'Apple AirPods Pro2.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sony WF-1000XM5 (Rtings)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Rtings/Bruel & Kjaer 5128 in-ear/Sony WF-1000XM5',
|
||||||
|
fileName: 'Sony WF-1000XM5.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Samsung Galaxy Buds3 Pro (Rtings)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds3 Pro',
|
||||||
|
fileName: 'Samsung Galaxy Buds3 Pro.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sennheiser HD 600 (Rtings)',
|
||||||
|
type: 'over-ear',
|
||||||
|
path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sennheiser HD 600',
|
||||||
|
fileName: 'Sennheiser HD 600.csv',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Static fallback list in case GitHub API fails — popular picks + additional well-known models
|
// Static fallback list in case GitHub API fails — popular picks + additional well-known models
|
||||||
const FALLBACK_INDEX = [
|
const FALLBACK_INDEX = [
|
||||||
...POPULAR_HEADPHONES,
|
...POPULAR_HEADPHONES,
|
||||||
{ name: 'Sennheiser HD 600 (Filk)', type: 'over-ear', path: 'Filk/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' },
|
{
|
||||||
{ name: 'Sennheiser HD 600 (Innerfidelity)', type: 'over-ear', path: 'Innerfidelity/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' },
|
name: 'Sennheiser HD 600 (Filk)',
|
||||||
{ name: 'Samsung Galaxy Buds2 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds2 Pro', fileName: 'Samsung Galaxy Buds2 Pro.csv' },
|
type: 'over-ear',
|
||||||
{ name: 'Sony WF-1000XM5 (Kazi)', type: 'in-ear', path: 'Kazi/in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' },
|
path: 'Filk/over-ear/Sennheiser HD 600',
|
||||||
{ name: 'Samsung Galaxy Buds3 Pro (DHRME)', type: 'in-ear', path: 'DHRME/in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' },
|
fileName: 'Sennheiser HD 600.csv',
|
||||||
{ name: 'Apple AirPods Pro (Super Review)', type: 'in-ear', path: 'Super Review/in-ear/Apple AirPods Pro', fileName: 'Apple AirPods Pro.csv' },
|
},
|
||||||
{ name: 'Sennheiser HD 600 (2020) (Kuulokenurkka)', type: 'over-ear', path: 'Kuulokenurkka/over-ear/Sennheiser HD 600 (2020)', fileName: 'Sennheiser HD 600 (2020).csv' },
|
{
|
||||||
|
name: 'Sennheiser HD 600 (Innerfidelity)',
|
||||||
|
type: 'over-ear',
|
||||||
|
path: 'Innerfidelity/over-ear/Sennheiser HD 600',
|
||||||
|
fileName: 'Sennheiser HD 600.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Samsung Galaxy Buds2 Pro (Rtings)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds2 Pro',
|
||||||
|
fileName: 'Samsung Galaxy Buds2 Pro.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sony WF-1000XM5 (Kazi)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Kazi/in-ear/Sony WF-1000XM5',
|
||||||
|
fileName: 'Sony WF-1000XM5.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Samsung Galaxy Buds3 Pro (DHRME)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'DHRME/in-ear/Samsung Galaxy Buds3 Pro',
|
||||||
|
fileName: 'Samsung Galaxy Buds3 Pro.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Apple AirPods Pro (Super Review)',
|
||||||
|
type: 'in-ear',
|
||||||
|
path: 'Super Review/in-ear/Apple AirPods Pro',
|
||||||
|
fileName: 'Apple AirPods Pro.csv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sennheiser HD 600 (2020) (Kuulokenurkka)',
|
||||||
|
type: 'over-ear',
|
||||||
|
path: 'Kuulokenurkka/over-ear/Sennheiser HD 600 (2020)',
|
||||||
|
fileName: 'Sennheiser HD 600 (2020).csv',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,7 +99,11 @@ const FALLBACK_INDEX = [
|
||||||
*/
|
*/
|
||||||
async function fetchAutoEqIndex() {
|
async function fetchAutoEqIndex() {
|
||||||
// Migrate: remove old localStorage cache to free quota
|
// Migrate: remove old localStorage cache to free quota
|
||||||
try { localStorage.removeItem(OLD_LS_CACHE_KEY); } catch { /* ignore */ }
|
try {
|
||||||
|
localStorage.removeItem(OLD_LS_CACHE_KEY);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Try loading from IndexedDB cache
|
// 1. Try loading from IndexedDB cache
|
||||||
try {
|
try {
|
||||||
|
|
@ -61,7 +125,9 @@ async function fetchAutoEqIndex() {
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await fetch('https://api.github.com/repos/jaakkopasanen/AutoEq/git/trees/master?recursive=1', { signal: controller.signal });
|
response = await fetch('https://api.github.com/repos/jaakkopasanen/AutoEq/git/trees/master?recursive=1', {
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +139,9 @@ async function fetchAutoEqIndex() {
|
||||||
console.warn('[AutoEQ] GitHub API limit reached. Using stale cache.');
|
console.warn('[AutoEQ] GitHub API limit reached. Using stale cache.');
|
||||||
return cached.data;
|
return cached.data;
|
||||||
}
|
}
|
||||||
} catch { /* ignore */ }
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
console.warn('[AutoEQ] GitHub API error. Using fallback.');
|
console.warn('[AutoEQ] GitHub API error. Using fallback.');
|
||||||
return FALLBACK_INDEX;
|
return FALLBACK_INDEX;
|
||||||
}
|
}
|
||||||
|
|
@ -92,13 +160,15 @@ async function fetchAutoEqIndex() {
|
||||||
const fileNameLower = fileName.toLowerCase();
|
const fileNameLower = fileName.toLowerCase();
|
||||||
|
|
||||||
// Skip non-measurement files (EQ presets, not raw frequency response)
|
// Skip non-measurement files (EQ presets, not raw frequency response)
|
||||||
if (fileNameLower.includes('parametriceq') ||
|
if (
|
||||||
|
fileNameLower.includes('parametriceq') ||
|
||||||
fileNameLower.includes('fixedbandeq') ||
|
fileNameLower.includes('fixedbandeq') ||
|
||||||
fileNameLower.includes('graphiceq') ||
|
fileNameLower.includes('graphiceq') ||
|
||||||
fileNameLower.includes('convolution') ||
|
fileNameLower.includes('convolution') ||
|
||||||
fileNameLower.includes('fixed band eq') ||
|
fileNameLower.includes('fixed band eq') ||
|
||||||
fileNameLower.includes('parametric eq') ||
|
fileNameLower.includes('parametric eq') ||
|
||||||
fileNameLower.includes('graphic eq')) {
|
fileNameLower.includes('graphic eq')
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +214,9 @@ async function fetchAutoEqIndex() {
|
||||||
try {
|
try {
|
||||||
const cached = await db.getSetting(CACHE_KEY);
|
const cached = await db.getSetting(CACHE_KEY);
|
||||||
if (cached?.data) return cached.data;
|
if (cached?.data) return cached.data;
|
||||||
} catch { /* ignore */ }
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('[AutoEQ] Failed to fetch index:', err);
|
console.error('[AutoEQ] Failed to fetch index:', err);
|
||||||
}
|
}
|
||||||
|
|
@ -205,12 +277,12 @@ function searchHeadphones(query, entries, typeFilter = 'all', limit = 100) {
|
||||||
let filtered = entries;
|
let filtered = entries;
|
||||||
|
|
||||||
if (typeFilter !== 'all') {
|
if (typeFilter !== 'all') {
|
||||||
filtered = filtered.filter(e => e.type === typeFilter);
|
filtered = filtered.filter((e) => e.type === typeFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query && query.trim()) {
|
if (query && query.trim()) {
|
||||||
const lower = query.toLowerCase().trim();
|
const lower = query.toLowerCase().trim();
|
||||||
filtered = filtered.filter(e => e.name.toLowerCase().includes(lower));
|
filtered = filtered.filter((e) => e.name.toLowerCase().includes(lower));
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered.slice(0, limit);
|
return filtered.slice(0, limit);
|
||||||
|
|
|
||||||
|
|
@ -7910,6 +7910,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
||||||
background: rgb(255 255 255 / 0.5);
|
background: rgb(255 255 255 / 0.5);
|
||||||
border: 1px dashed rgb(255 255 255 / 0.3);
|
border: 1px dashed rgb(255 255 255 / 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-corrected .legend-dot {
|
.legend-corrected .legend-dot {
|
||||||
background: #f472b6;
|
background: #f472b6;
|
||||||
}
|
}
|
||||||
|
|
@ -8109,6 +8110,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
||||||
.autoeq-status.error {
|
.autoeq-status.error {
|
||||||
color: var(--destructive);
|
color: var(--destructive);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoeq-status.success {
|
.autoeq-status.success {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
@ -8941,12 +8943,15 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
||||||
.speaker-eq-slider-value.bass {
|
.speaker-eq-slider-value.bass {
|
||||||
color: #22d3ee;
|
color: #22d3ee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.speaker-eq-slider-value.room {
|
.speaker-eq-slider-value.room {
|
||||||
color: #f59e0b;
|
color: #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.speaker-eq-section .autoeq-control-label.bass {
|
.speaker-eq-section .autoeq-control-label.bass {
|
||||||
color: #22d3ee;
|
color: #22d3ee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.speaker-eq-section .autoeq-control-label.room {
|
.speaker-eq-section .autoeq-control-label.room {
|
||||||
color: #f59e0b;
|
color: #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue