Add centre-panned vocal reduction to live and saved audio

This commit is contained in:
Jonathan Sykes
2026-10-03 14:59:44 +08:00
parent 7d84fdb3dc
commit de89508bf4
6 changed files with 41 additions and 5 deletions

View File

@@ -6540,8 +6540,9 @@ const EqRender = (() => {
const g = EQ.gains();
const lufs = Level.enabled() ? Level.remembered(id) : null;
const levelDb = lufs == null ? 0 : Math.round(Level.gainFor(lufs) * 2) / 2;
if (g.every((v) => v === 0) && Math.abs(levelDb) < 0.5) return null;
return { gains: g, levelDb, sig: g.join(',') + '|' + levelDb };
const vocal = VocalReducer.amount(data.settings.vocalLevel ?? 100);
if (g.every((v) => v === 0) && Math.abs(levelDb) < 0.5 && vocal === 100) return null;
return { gains: g, levelDb, vocal, sig: g.join(',') + '|' + levelDb + '|vocal:' + vocal };
}
async function dir() { return (await navigator.storage.getDirectory()).getDirectoryHandle(DIR, { create: true }); }
async function drop(id) {
@@ -6609,7 +6610,8 @@ const EqRender = (() => {
const gain = off.createGain(); gain.gain.value = Math.pow(10, w.levelDb / 20);
const lim = off.createDynamicsCompressor();
lim.threshold.value = -1; lim.knee.value = 0; lim.ratio.value = 20; lim.attack.value = 0.003; lim.release.value = 0.25;
node.connect(gain); gain.connect(lim); lim.connect(off.destination);
const vocal = VocalReducer.create(off, node, w.vocal);
vocal.output.connect(gain); gain.connect(lim); lim.connect(off.destination);
src.start();
const out = await off.startRendering();
if (wanted(id)?.sig !== w.sig) return; // settings changed meanwhile — the queue re-runs it
@@ -6741,7 +6743,8 @@ const EQ = (() => {
st.limiter = st.ctx.createDynamicsCompressor();
st.limiter.threshold.value = -1; st.limiter.knee.value = 0; st.limiter.ratio.value = 20;
st.limiter.attack.value = 0.003; st.limiter.release.value = 0.25;
st.filters[st.filters.length - 1].connect(st.level);
st.vocal = VocalReducer.create(st.ctx, st.filters[st.filters.length - 1]);
st.vocal.output.connect(st.level);
st.level.connect(st.limiter);
st.limiter.connect(st.ctx.destination);
return true;
@@ -6760,12 +6763,13 @@ const EQ = (() => {
// Apply the stored curve; wires the graph up the first time it's non-flat.
function apply() {
if (!available()) return;
if (!st.ctx && isFlat()) return;
if (!st.ctx && isFlat() && VocalReducer.amount(data.settings.vocalLevel ?? 100) === 100) return;
if (!build()) return;
attach(els.video);
attach(els.audio);
const g = rendered() ? EQ_BANDS.map(() => 0) : gains();
st.filters.forEach((f, i) => { f.gain.value = g[i]; });
st.vocal.set(rendered() ? 100 : data.settings.vocalLevel ?? 100);
if (st.ctx.state === 'suspended') st.ctx.resume().catch(() => {});
renderButton();
}
@@ -12457,6 +12461,7 @@ async function boot() {
LyricsWindow.configure({ prepare: () => { if (!isServiceModeOpen()) Notes.stageLyrics.set(true); }, view: () => isServiceModeOpen() && data.settings.serviceLyrics ? $('serviceLyrics') : $('stageLyrics'), line: () => $('serviceLyricsList')?.querySelector('.active')?.textContent || $('stageLyricsList')?.querySelector('.active')?.textContent || current?.meta?.title || 'Nothing playing', toggle: () => { Player.toggle(); updatePlayBtn(); }, next: playNext, refresh: () => { Notes.stageLyrics.render(true); Notes.serviceRefresh(); }, toast });
EQ.renderButton();
EQ.apply();
VocalReducer.register({ settings: () => data.settings, persist, apply: () => { EQ.apply(); EqRender.scheduleAll(); } });
MidiPedals.configure({ settings: () => data.settings, persist, action: action => {
if (action === 'toggle') { Player.toggle(); updatePlayBtn(); }
else if (action === 'next') playNext();