Add JamesDSP playback controls and verification harness
This commit is contained in:
@@ -675,10 +675,14 @@ const DEFAULT_SETTINGS = {
|
||||
};
|
||||
function normalizeJamesDspSettings(settings) {
|
||||
const saved = settings?.jamesDsp && typeof settings.jamesDsp === 'object' ? settings.jamesDsp : {};
|
||||
const params = saved.params && typeof saved.params === 'object' && !Array.isArray(saved.params) ? { ...saved.params } : {};
|
||||
// Impulse-response PCM is intentionally session-only; do not re-enable the
|
||||
// convolver on a later boot without its uploaded audio buffer.
|
||||
delete params['convolver.enabled'];
|
||||
settings.jamesDsp = {
|
||||
enabled: saved.enabled === true,
|
||||
preset: typeof saved.preset === 'string' ? saved.preset : 'flat',
|
||||
params: saved.params && typeof saved.params === 'object' && !Array.isArray(saved.params) ? { ...saved.params } : {},
|
||||
params,
|
||||
};
|
||||
return settings;
|
||||
}
|
||||
@@ -3346,6 +3350,20 @@ function openMoreActions() {
|
||||
row.onclick = () => { closeModal(); setTimeout(() => src.click(), 0); };
|
||||
list.appendChild(row);
|
||||
}
|
||||
if (data.settings?.jamesDsp?.enabled && EQ.state().jamesDsp?.active) {
|
||||
const row = document.createElement('button');
|
||||
row.type = 'button'; row.className = 'more-row';
|
||||
const bypassed = !!EQ.state().jamesDsp?.bypassed;
|
||||
row.innerHTML = '<span class="more-ico" aria-hidden="true">A/B</span><span class="more-txt"><b></b><small>Temporarily compare with the unprocessed signal</small></span><span class="more-go">›</span>';
|
||||
row.querySelector('b').textContent = bypassed ? 'Resume JamesDSP' : 'Bypass JamesDSP';
|
||||
row.setAttribute('aria-label', row.querySelector('b').textContent);
|
||||
row.onclick = async () => {
|
||||
closeModal();
|
||||
const next = await EQ.toggleDspBypass();
|
||||
toast(next ? 'JamesDSP bypassed' : 'JamesDSP active');
|
||||
};
|
||||
list.appendChild(row);
|
||||
}
|
||||
if (!list.children.length) { toast('No more actions for this video'); return; }
|
||||
showModal('More actions', list, [{ label: 'Close', onClick: closeModal }]);
|
||||
}
|
||||
@@ -5521,7 +5539,7 @@ const EQ = (() => {
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
const AC = window.AudioContext || window.webkitAudioContext;
|
||||
const st = { ctx: null, filters: null, levelDb: 0 };
|
||||
let dspController = null, dspControllerPromise = null;
|
||||
let dspController = null, dspControllerPromise = null, dspRequest = 0;
|
||||
// Web Audio is off on iOS (it silences playback when the screen locks)
|
||||
// unless the user opted in through "Level volume" (Settings → Playback).
|
||||
// Live Web Audio is off on iOS (it silences playback when the screen locks)
|
||||
@@ -5554,6 +5572,8 @@ const EQ = (() => {
|
||||
function dspFailed(error) {
|
||||
if (!data.settings.jamesDsp?.enabled) return;
|
||||
data.settings.jamesDsp.enabled = false;
|
||||
if ($('setSoundEngine')) $('setSoundEngine').value = 'standard';
|
||||
if ($('jamesDspControls')) $('jamesDspControls').hidden = true;
|
||||
persist();
|
||||
toast(`JamesDSP unavailable; Standard sound is active. ${error?.message || ''}`.trim());
|
||||
apply();
|
||||
@@ -5562,15 +5582,19 @@ const EQ = (() => {
|
||||
|
||||
async function getDspController() {
|
||||
if (dspController) return dspController;
|
||||
if (!dspControllerPromise) dspControllerPromise = Lazy.load('feature:jamesdsp').then(() => {
|
||||
dspController = JamesDSPFeature.createController({ graph: AudioGraph, prepareGraph: prepareDspGraph, onError: dspFailed });
|
||||
if (!dspControllerPromise) dspControllerPromise = Lazy.load('feature:jamesdsp').then(async () => {
|
||||
dspController = await JamesDSPFeature.createController({ graph: AudioGraph, prepareGraph: prepareDspGraph, onError: dspFailed });
|
||||
return dspController;
|
||||
}).catch(error => { dspControllerPromise = null; throw error; });
|
||||
return dspControllerPromise;
|
||||
}
|
||||
|
||||
function startDsp() {
|
||||
getDspController().then(controller => controller.enable(data.settings.jamesDsp, st.levelDb)).catch(dspFailed);
|
||||
const request = ++dspRequest;
|
||||
getDspController().then(controller => {
|
||||
if (request !== dspRequest || !data.settings.jamesDsp?.enabled) return controller.disable();
|
||||
return controller.enable(data.settings.jamesDsp, st.levelDb);
|
||||
}).catch(dspFailed);
|
||||
}
|
||||
|
||||
function build() {
|
||||
@@ -5620,7 +5644,9 @@ const EQ = (() => {
|
||||
renderButton();
|
||||
return;
|
||||
}
|
||||
dspRequest++;
|
||||
if (dspController) dspController.disable().catch(() => {});
|
||||
else if (dspControllerPromise) dspControllerPromise.then(controller => controller.disable()).catch(() => {});
|
||||
if (!available()) return;
|
||||
if (!st.ctx && isFlat() && EqParameters.amount(data.settings.vocalLevel ?? 100) === 100 && EqParameters.width(data.settings.stereoWidth) === 0) return;
|
||||
if (!build()) return;
|
||||
@@ -5733,8 +5759,22 @@ const EQ = (() => {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function toggleDspBypass() {
|
||||
if (!dspController?.active) return false;
|
||||
try { return await dspController.toggleBypass(); }
|
||||
catch (error) { dspFailed(error); return false; }
|
||||
}
|
||||
|
||||
async function loadDspIR(file) {
|
||||
if (!data.settings.jamesDsp?.enabled || !dspController?.active) throw new Error('Turn on JamesDSP before importing an impulse response.');
|
||||
const context = st.ctx;
|
||||
if (!context?.decodeAudioData) throw new Error('Audio decoding is unavailable on this device.');
|
||||
const audioBuffer = await context.decodeAudioData(await file.arrayBuffer());
|
||||
return dspController.loadIR(audioBuffer);
|
||||
}
|
||||
|
||||
return { open, apply, renderButton, available, analyser, setLevel, gains, bands: EQ_BANDS, isIOS: () => isIOS, state: () => ({ wired: !!st.ctx, ctx: st.ctx && st.ctx.state, gains: st.filters ? st.filters.map((f) => f.gain.value) : null,
|
||||
levelDb: st.level ? Math.round(200 * Math.log10(Math.max(1e-6, st.level.gain.value))) / 10 : null }) };
|
||||
levelDb: st.level ? Math.round(200 * Math.log10(Math.max(1e-6, st.level.gain.value))) / 10 : null, jamesDsp: dspController?.state() || null }), toggleDspBypass, loadDspIR };
|
||||
})();
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user