/* Shared live/offline EQ definitions and legacy curve migration. */ (function(root) { 'use strict'; const frequencies = [31,62,125,250,500,1000,2000,4000,8000,16000]; const bands = frequencies.map((f,i) => ({ f, label: f >= 1000 ? `${f/1000}k` : String(f), type: i === 0 ? 'lowshelf' : i === 9 ? 'highshelf' : 'peaking' })); const clamp = n => Math.max(-12, Math.min(12, Number(n) || 0)); function migrate(values) { if (!Array.isArray(values)) return frequencies.map(() => 0); if (values.length !== 5) return frequencies.map((_,i) => clamp(values[i])); const old = [60,250,1000,4000,12000], curve = values.map(clamp); return frequencies.map(f => { if (f <= old[0]) return curve[0]; if (f >= old[4]) return curve[4]; const i = old.findIndex(x => x >= f), weight = Math.log(f / old[i-1]) / Math.log(old[i] / old[i-1]); return Math.round((curve[i-1] * (1-weight) + curve[i] * weight) * 10) / 10; }); } const presets = { flat: { name: 'Flat', g: frequencies.map(() => 0) }, bass: { name: 'Bass boost', g: [5,7,5,3,1,0,0,0,0,0] }, vocal: { name: 'Vocal clarity', g: [-3,-4,-2,0,1,2,3,4,1,0] }, acoustic: { name: 'Acoustic live', g: [-2,-1,0,-2,-1,1,2,2,1,0] }, classical: { name: 'Classical', g: migrate([4,2,-1,1,3]) }, treble: { name: 'Treble boost', g: [0,0,0,0,0,0,1,3,5,6] }, }; const width = value => Math.max(0, Math.min(100, Number(value) || 0)); function createWidener(context, source, value = 0) { const splitter = context.createChannelSplitter(2), output = context.createChannelMerger(2), delay = context.createDelay(.05); source.connect(splitter); splitter.connect(output, 0, 0); splitter.connect(delay, 1); delay.connect(output, 0, 1); delay.delayTime.value = width(value) / 100 * .015; return { output, set: next => delay.delayTime.setTargetAtTime(width(next) / 100 * .015, context.currentTime, .03) }; } function register(adapter) { root.SettingsSections.register({ id: 'equalizer', title: 'Equalizer & stereo', cluster: 'Listening', summary: () => `${presets[adapter.settings().eq?.preset]?.name || 'Custom'} ยท ${width(adapter.settings().stereoWidth) ? 'Widened' : 'Original stereo'}`, render(container) { const doc = container.ownerDocument, button = doc.createElement('button'); button.type = 'button'; button.className = 'settings-back'; button.textContent = 'Open ten-band equalizer'; button.onclick = adapter.open; const label = doc.createElement('label'); label.className = 'set-row'; label.textContent = 'Headphone stereo widener (optional)'; const input = doc.createElement('input'); input.type = 'range'; input.min = '0'; input.max = '100'; input.value = String(width(adapter.settings().stereoWidth)); input.setAttribute('aria-label', 'Headphone stereo width'); const out = doc.createElement('output'); out.textContent = `${input.value}%`; input.oninput = () => { adapter.settings().stereoWidth = Number(input.value); out.textContent = `${input.value}%`; adapter.persist(); adapter.apply(); }; label.append(input, out); const note = doc.createElement('p'); note.textContent = 'Off at 0%. Adds up to 15 ms delay to the right channel for headphones. May colour the sound on speakers or when summed to mono. On iPhone, saved songs use processed copies; streamed songs require Live sound processing.'; container.append(button,label,note); } }); } const api = { bands, presets, migrate, width, createWidener, register }; if (typeof module !== 'undefined') module.exports = api; else root.EqCore = api; })(typeof window !== 'undefined' ? window : globalThis);