diff --git a/js/audio-context.js b/js/audio-context.js index 364e134..bf703e3 100644 --- a/js/audio-context.js +++ b/js/audio-context.js @@ -718,6 +718,23 @@ class AudioContextManager { /** * Set gain for a specific band */ + /** + * Replace an IIR shelf filter node with updated coefficients and reconnect the chain. + */ + _replaceShelfFilter(index) { + const filter = this.filters[index]; + if (!filter?._shelfType || !this.audioContext) return; + const type = this.currentTypes?.[index] || filter._shelfType; + const freq = this.frequencies[index]; + const gain = this.currentGains[index] || 0; + const q = this.currentQs?.[index] > 0 ? this.currentQs[index] : this._calculateQ(index); + const coeffs = computeShelfCoefficients(type, freq, gain, q, this.audioContext.sampleRate); + const iir = this.audioContext.createIIRFilter(coeffs.feedforward, coeffs.feedback); + iir._shelfType = type; + try { filter.disconnect(); } catch { /* ignore */ } + this.filters[index] = iir; + } + setBandGain(bandIndex, gainDb) { if (bandIndex < 0 || bandIndex >= this.bandCount) return; @@ -725,8 +742,13 @@ class AudioContextManager { this.currentGains[bandIndex] = clampedGain; if (this.filters[bandIndex] && this.audioContext) { - const now = this.audioContext.currentTime; - this.filters[bandIndex].gain.setTargetAtTime(clampedGain, now, 0.01); + if (this.filters[bandIndex]._shelfType) { + this._replaceShelfFilter(bandIndex); + this._connectGraph(); + } else { + const now = this.audioContext.currentTime; + this.filters[bandIndex].gain.setTargetAtTime(clampedGain, now, 0.01); + } } equalizerSettings.setGains(this.currentGains); @@ -745,16 +767,26 @@ class AudioContextManager { } const now = this.audioContext?.currentTime || 0; + let needsReconnect = false; adjustedGains.forEach((gain, index) => { const clampedGain = this._clampGain(gain); this.currentGains[index] = clampedGain; if (this.filters[index]) { - this.filters[index].gain.setTargetAtTime(clampedGain, now, 0.01); + if (this.filters[index]._shelfType) { + this._replaceShelfFilter(index); + needsReconnect = true; + } else { + this.filters[index].gain.setTargetAtTime(clampedGain, now, 0.01); + } } }); + if (needsReconnect) { + this._connectGraph(); + } + equalizerSettings.setGains(this.currentGains); } @@ -949,15 +981,9 @@ class AudioContextManager { sortedBands.forEach((band, index) => { if (index >= count) return; const filterType = band.type === 'lowshelf' ? 'LSC' : band.type === 'highshelf' ? 'HSC' : 'PK'; - if (band.type === 'lowshelf' || band.type === 'highshelf') { - lines.push( - `Filter ${index + 1}: ON ${filterType} Fc ${newFrequencies[index]} Hz Gain ${newGains[index].toFixed(1)} dB` - ); - } else { - lines.push( - `Filter ${index + 1}: ON ${filterType} Fc ${newFrequencies[index]} Hz Gain ${newGains[index].toFixed(1)} dB Q ${newQs[index].toFixed(2)}` - ); - } + lines.push( + `Filter ${index + 1}: ON ${filterType} Fc ${newFrequencies[index]} Hz Gain ${newGains[index].toFixed(1)} dB Q ${newQs[index].toFixed(2)}` + ); }); return lines.join('\n'); @@ -976,17 +1002,11 @@ class AudioContextManager { const gain = this.currentGains[index] || 0; const type = (this.currentTypes && this.currentTypes[index]) || 'peaking'; const filterType = type === 'lowshelf' ? 'LSC' : type === 'highshelf' ? 'HSC' : 'PK'; + const q = this.currentQs && this.currentQs[index] > 0 ? this.currentQs[index] : this._calculateQ(index); const filterNum = index + 1; - if (type === 'lowshelf' || type === 'highshelf') { - lines.push( - `Filter ${filterNum}: ON ${filterType} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB` - ); - } else { - const q = this.currentQs && this.currentQs[index] > 0 ? this.currentQs[index] : this._calculateQ(index); - lines.push( - `Filter ${filterNum}: ON ${filterType} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}` - ); - } + lines.push( + `Filter ${filterNum}: ON ${filterType} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}` + ); }); return lines.join('\n'); @@ -1067,7 +1087,8 @@ class AudioContextManager { this._connectGraph(); } - // Persist all band settings + // Persist all band settings including custom frequencies + equalizerSettings.setCustomFrequencies(this.frequencies); equalizerSettings.setGains(this.currentGains); equalizerSettings.setBandTypes(this.currentTypes); equalizerSettings.setBandQs(this.currentQs); diff --git a/js/equalizer.js b/js/equalizer.js index 0186f5d..f02101b 100644 --- a/js/equalizer.js +++ b/js/equalizer.js @@ -625,13 +625,9 @@ export class Equalizer { const type = filter ? filter.type : 'peaking'; const typeMap = { peaking: 'PK', lowshelf: 'LSC', highshelf: 'HSC' }; const typeStr = typeMap[type] || 'PK'; + const q = filter && !filter._shelfType ? filter.Q.value : this._calculateQ(index); const filterNum = index + 1; - if (type === 'lowshelf' || type === 'highshelf') { - lines.push(`Filter ${filterNum}: ON ${typeStr} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB`); - } else { - const q = filter ? filter.Q.value : this._calculateQ(index); - lines.push(`Filter ${filterNum}: ON ${typeStr} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}`); - } + lines.push(`Filter ${filterNum}: ON ${typeStr} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}`); }); return lines.join('\n'); diff --git a/js/settings.js b/js/settings.js index babe0b4..d36d9b9 100644 --- a/js/settings.js +++ b/js/settings.js @@ -2210,17 +2210,23 @@ export async function initializeSettings(scrobbler, player, api, ui) { // Show tooltip on drag if (isDragged) { ctx.save(); - ctx.fillStyle = 'rgba(0,0,0,0.8)'; - const txt = `${Math.round(band.freq)} Hz ${band.gain > 0 ? '+' : ''}${band.gain.toFixed(1)} dB Q${band.q.toFixed(2)}`; ctx.font = 'bold 11px system-ui, sans-serif'; - const tw = ctx.measureText(txt).width + 12; - const tx = Math.min(x - tw / 2, rect.width - tw - 5); - const ty = y - 28; - ctx.fillRect(tx, ty, tw, 20); + const line1 = `${Math.round(band.freq)} Hz ${band.gain > 0 ? '+' : ''}${band.gain.toFixed(1)} dB Q${band.q.toFixed(2)}`; + const line2 = `Sum: ${nodeGain > 0 ? '+' : ''}${nodeGain.toFixed(1)} dB`; + const tw1 = ctx.measureText(line1).width; + const tw2 = ctx.measureText(line2).width; + const tw = Math.max(tw1, tw2) + 12; + const th = 34; + const tx = Math.max(5, Math.min(x - tw / 2, rect.width - tw - 5)); + const ty = y - 44; + ctx.fillStyle = 'rgba(0,0,0,0.8)'; + ctx.fillRect(tx, ty, tw, th); ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(txt, tx + tw / 2, ty + 10); + ctx.fillText(line1, tx + tw / 2, ty + 10); + ctx.fillStyle = 'rgba(255,255,255,0.7)'; + ctx.fillText(line2, tx + tw / 2, ty + 24); ctx.restore(); } });