Spaces:
Running
Running
Commit ·
51e9bed
1
Parent(s): cc88aa8
put score-player.js at front of other js scripts.
Browse files- app.py +14 -5
- web/score-player.js +29 -6
app.py
CHANGED
|
@@ -403,17 +403,26 @@ def build_head ():
|
|
| 403 |
the vendored copy. Gradio delivers this via its client config and injects it
|
| 404 |
on the frontend, so absolute `/gradio_api/file=` URLs resolve correctly.'''
|
| 405 |
vendor = os.path.join(WEB_DIR, 'vendor')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
scripts = [
|
| 407 |
-
os.path.join(
|
| 408 |
-
os.path.join(vendor, '
|
|
|
|
| 409 |
os.path.join(vendor, 'musicWidgetsBrowser.umd.min.js'),
|
| 410 |
# FluidSynth audio backend: js-synthesizer UMD (window.JSSynth) + our adapter
|
| 411 |
-
# (window.LilyFluidAudio).
|
| 412 |
-
#
|
| 413 |
# loaded on demand by the adapter from web/fluid/ and web/soundfont/.
|
| 414 |
os.path.join(vendor, 'js-synthesizer.min.js'),
|
| 415 |
os.path.join(WEB_DIR, 'fluid-audio.js'),
|
| 416 |
-
os.path.join(WEB_DIR, 'score-player.js'),
|
| 417 |
# our own CodeMirror 6 editor (CM + grammar-derived lilylet() highlighter) and
|
| 418 |
# the mount/bridge script that wires it to the hidden #ls-editor-state textbox.
|
| 419 |
os.path.join(vendor, 'lyl-editor.bundle.js'),
|
|
|
|
| 403 |
the vendored copy. Gradio delivers this via its client config and injects it
|
| 404 |
on the frontend, so absolute `/gradio_api/file=` URLs resolve correctly.'''
|
| 405 |
vendor = os.path.join(WEB_DIR, 'vendor')
|
| 406 |
+
# ORDER MATTERS for cold-load UX. score-player.js (28KB) defines window.LilyScore
|
| 407 |
+
# and self-mounts the player as soon as it executes, replacing the loading spinner.
|
| 408 |
+
# The heavy deps it uses — verovio.bundle.js (7.6MB!), music-widgets, the FluidSynth
|
| 409 |
+
# adapter — are accessed LAZILY (initVerovio/initAudio await their globals via
|
| 410 |
+
# waitForGlobal), so they must NOT block score-player.js from running. We therefore
|
| 411 |
+
# load score-player.js FIRST; if it sat behind verovio (synchronous <head> order),
|
| 412 |
+
# a slow cold load would leave the spinner stuck for the whole 7.6MB download before
|
| 413 |
+
# LilyScore — and the self-mount inside it — could run. lilylet.bundle.js stays ahead
|
| 414 |
+
# because lylToMei() needs window.LilyletLib synchronously at render time (no waiter).
|
| 415 |
scripts = [
|
| 416 |
+
os.path.join(WEB_DIR, 'score-player.js'), # defines LilyScore + self-mounts (load first!)
|
| 417 |
+
os.path.join(vendor, 'lilylet.bundle.js'), # window.LilyletLib (used synchronously by lylToMei)
|
| 418 |
+
os.path.join(vendor, 'verovio.bundle.js'), # 7.6MB; awaited lazily via VerovioInit
|
| 419 |
os.path.join(vendor, 'musicWidgetsBrowser.umd.min.js'),
|
| 420 |
# FluidSynth audio backend: js-synthesizer UMD (window.JSSynth) + our adapter
|
| 421 |
+
# (window.LilyFluidAudio). score-player.js awaits these lazily in initAudio()
|
| 422 |
+
# (no longer a hard ordering requirement). The libfluidsynth runtime + gm.sf3 are
|
| 423 |
# loaded on demand by the adapter from web/fluid/ and web/soundfont/.
|
| 424 |
os.path.join(vendor, 'js-synthesizer.min.js'),
|
| 425 |
os.path.join(WEB_DIR, 'fluid-audio.js'),
|
|
|
|
| 426 |
# our own CodeMirror 6 editor (CM + grammar-derived lilylet() highlighter) and
|
| 427 |
# the mount/bridge script that wires it to the hidden #ls-editor-state textbox.
|
| 428 |
os.path.join(vendor, 'lyl-editor.bundle.js'),
|
web/score-player.js
CHANGED
|
@@ -50,17 +50,38 @@
|
|
| 50 |
|
| 51 |
function log (msg) { console.log('[LilyScore]', msg); }
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
// ---- Verovio init -------------------------------------------------------
|
| 54 |
|
| 55 |
async function initVerovio () {
|
| 56 |
if (state.verovioReady) return state.toolkit;
|
| 57 |
if (state._verovioInitPromise) return state._verovioInitPromise;
|
| 58 |
-
|
| 59 |
-
//
|
| 60 |
-
// returns a constructed toolkit — the path proven by lilylet-live-editor.
|
| 61 |
state._verovioInitPromise = (async function () {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
try {
|
| 63 |
-
const tk = await
|
| 64 |
tk.setOptions({ scale: 40, adjustPageHeight: true, breaks: 'auto', pageWidth: 2100, pageHeight: 2970 });
|
| 65 |
state.toolkit = tk;
|
| 66 |
state.verovioReady = true;
|
|
@@ -177,8 +198,10 @@
|
|
| 177 |
|
| 178 |
async function initAudio () {
|
| 179 |
if (state.audioReady) return true;
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
| 182 |
// MIDI/MidiPlayer/MusicNotation come from music-widgets; the *audio backend* is
|
| 183 |
// FluidSynth (js-synthesizer + gm.sf3, 128 GM instruments) via LilyFluidAudio,
|
| 184 |
// which falls back to music-widgets' single-piano MidiAudio while the large GM
|
|
|
|
| 50 |
|
| 51 |
function log (msg) { console.log('[LilyScore]', msg); }
|
| 52 |
|
| 53 |
+
// Wait for a global (set by a vendored <script>) to appear. Resolves with the
|
| 54 |
+
// value, or null on timeout. Needed because score-player.js now loads BEFORE the
|
| 55 |
+
// 7.6MB verovio.bundle.js in <head> (so LilyScore is defined — and the player
|
| 56 |
+
// mounts — early on a cold/slow load, instead of being blocked behind verovio's
|
| 57 |
+
// download). The verovio-dependent paths therefore can't assume window.VerovioInit
|
| 58 |
+
// exists yet; they await it here. Poll is cheap (100ms) and self-clearing.
|
| 59 |
+
function waitForGlobal (name, timeoutMs) {
|
| 60 |
+
if (window[name]) return Promise.resolve(window[name]);
|
| 61 |
+
return new Promise(function (resolve) {
|
| 62 |
+
var start = Date.now();
|
| 63 |
+
var iv = setInterval(function () {
|
| 64 |
+
if (window[name]) { clearInterval(iv); resolve(window[name]); }
|
| 65 |
+
else if (timeoutMs && Date.now() - start > timeoutMs) { clearInterval(iv); resolve(null); }
|
| 66 |
+
}, 100);
|
| 67 |
+
});
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
// ---- Verovio init -------------------------------------------------------
|
| 71 |
|
| 72 |
async function initVerovio () {
|
| 73 |
if (state.verovioReady) return state.toolkit;
|
| 74 |
if (state._verovioInitPromise) return state._verovioInitPromise;
|
| 75 |
+
// Set the promise BEFORE the first await so concurrent callers (the mount-time
|
| 76 |
+
// warmup + the first render) share ONE init and we never construct verovio twice.
|
|
|
|
| 77 |
state._verovioInitPromise = (async function () {
|
| 78 |
+
// verovio.bundle.js now loads after us; wait (up to 5 min) for its global.
|
| 79 |
+
const VInit = await waitForGlobal('VerovioInit', 300000);
|
| 80 |
+
if (!VInit) { log('VerovioInit global missing (timeout)'); state._verovioInitPromise = null; return null; }
|
| 81 |
+
// VerovioInit() awaits the Emscripten WASM module's readyPromise, then
|
| 82 |
+
// returns a constructed toolkit — the path proven by lilylet-live-editor.
|
| 83 |
try {
|
| 84 |
+
const tk = await VInit();
|
| 85 |
tk.setOptions({ scale: 40, adjustPageHeight: true, breaks: 'auto', pageWidth: 2100, pageHeight: 2970 });
|
| 86 |
state.toolkit = tk;
|
| 87 |
state.verovioReady = true;
|
|
|
|
| 198 |
|
| 199 |
async function initAudio () {
|
| 200 |
if (state.audioReady) return true;
|
| 201 |
+
// music-widgets + the FluidSynth adapter (js-synthesizer.min.js + fluid-audio.js)
|
| 202 |
+
// load after us in <head>; wait for the music-widgets global to appear.
|
| 203 |
+
const mw = await waitForGlobal('musicWidgetsBrowser', 300000);
|
| 204 |
+
if (!mw) { log('musicWidgetsBrowser missing (timeout)'); return false; }
|
| 205 |
// MIDI/MidiPlayer/MusicNotation come from music-widgets; the *audio backend* is
|
| 206 |
// FluidSynth (js-synthesizer + gm.sf3, 128 GM instruments) via LilyFluidAudio,
|
| 207 |
// which falls back to music-widgets' single-piano MidiAudio while the large GM
|