# -*- coding: utf-8 -*- # Patch: crossfade activation/deactivation master chain FX (het crackle click) # Applies to app/static/js/app.jsx + app/static/js/sf-fx-realtime.js (CRLF). import io, sys def patch(path, repls, expect_all_multi=False): with io.open(path, 'r', encoding='utf-8', newline='') as f: src = f.read() for old, new, count in repls: n = src.count(old) if count is None: if n < 1: print('MISS (expected >=1):', old[:80]); sys.exit(1) src = src.replace(old, new) else: if n != count: print('BAD COUNT %d (want %d): %s' % (n, count, old[:80])); sys.exit(1) src = src.replace(old, new) with io.open(path, 'w', encoding='utf-8', newline='') as f: f.write(src) print('PATCHED', path) # ── app.jsx ───────────────────────────────────────────────────────────────── appjsx = 'app/static/js/app.jsx' A = [ # 1. fxRt state: them fadedIn flag ("const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, ws: null, node: null, inQueue: [], pumpTimer: null, lastInSent: 0, chainKey: null, latencyTotal: 0 };", "const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, ws: null, node: null, inQueue: [], pumpTimer: null, lastInSent: 0, chainKey: null, latencyTotal: 0, fadedIn: false };", 1), # 2a. initMasterBus: tao 2 gain crossfade (" // Global fader / output\n const output = ctx.createGain();\n output.gain.value = 1.0;", " // Global fader / output\n const output = ctx.createGain();\n output.gain.value = 1.0;\n\n // Crossfade master-FX activation (het click khi bat/tat master chain FX):\n // path mac dinh outputAnalyser -> masterFxDryGain(1) -> pdcDelay -> output;\n // nhanh FX noi vao masterFxGain(0) -> output. fxRtStart/fxRtStop ramp 2 gain\n // nay thay vi hard-rewire graph (dry path luon noi san — no click, no gap\n // trong luc worklet FILL ~86ms).\n const masterFxDryGain = ctx.createGain();\n masterFxDryGain.gain.value = 1.0;\n const masterFxGain = ctx.createGain();\n masterFxGain.gain.value = 0.0;", 1), # 2b. masterBus object literal (" analyser,\n output,\n masteringActive: false,", " analyser,\n output,\n masterFxDryGain,\n masterFxGain,\n masteringActive: false,", 1), # 2c. wire graph: dry path qua masterFxDryGain -> pdcDelay -> output (" masterBus.input.connect(masterBus.inputAnalyser);\n masterBus.inputAnalyser.connect(masterBus.outputAnalyser);\n masterBus.outputAnalyser.connect(masterBus.output);", " masterBus.input.connect(masterBus.inputAnalyser);\n masterBus.inputAnalyser.connect(masterBus.outputAnalyser);\n masterBus.outputAnalyser.connect(masterBus.masterFxDryGain);\n masterBus.masterFxDryGain.connect(masterBus.pdcDelay);\n masterBus.outputAnalyser.connect(masterBus.masterFxGain);\n masterBus.masterFxGain.connect(masterBus.output);", 1), # 3. latency: chi ap PDC sau khi crossfade xong (dry silent) — ap som = gap 80ms (" if (d && d.cmd === 'latency' && typeof d.total === 'number') {\n fxRt.latencyTotal = d.total;\n fxRtApplyPdc();\n }", " if (d && d.cmd === 'latency' && typeof d.total === 'number') {\n fxRt.latencyTotal = d.total;\n // Chi delay dry path khi crossfade da xong (dry silent) — ap som\n // trong luc dry con la path nghe duoc se tao gap ~80ms.\n if (fxRt.fadedIn) fxRtApplyPdc();\n }", 1), # 4. guard start khi masterBus moi thieu gains (recovery) (" if (!ctx || !ctx.audioWorklet || !masterBus || !masterBus.output || !masterBus.analyser) return;", " if (!ctx || !ctx.audioWorklet || !masterBus || !masterBus.output || !masterBus.analyser || !masterBus.masterFxGain || !masterBus.masterFxDryGain) return;", 1), # 5. onmessage: xu ly 'started' -> crossfade(true) + PDC sau khi dry silent (" fxRt.node.port.onmessage = (ev) => {\n const d = ev.data;\n if (!d || !fxRt.ws) return;\n if (d.type === 'set_param') {\n try { fxRt.ws.send(JSON.stringify({ cmd: 'set_param', slot: d.slot, key: d.key, value: d.value })); } catch (e) {}\n return;\n }\n if (d.type !== 'in') return;", " fxRt.node.port.onmessage = (ev) => {\n const d = ev.data;\n if (!d || !fxRt.ws) return;\n if (d.type === 'started') {\n // Worklet bat dau phat (sau FILL) -> crossfade dry->fx muot. Token guard\n // chong race: neu stop/restart xay ra trong luc cho, bo qua.\n if (fxRt.fadedIn) return;\n setTimeout(() => {\n if (token !== fxRt.startToken) return;\n fxRt.fadedIn = true;\n fxRtCrossfade(true, token);\n // Dry gan silent (tc 0.025) roi moi tre pdcDelay — tranh gap khi delay\n // dang lon dan.\n setTimeout(() => fxRtApplyPdc(), 250);\n }, 30);\n return;\n }\n if (d.type === 'set_param') {\n try { fxRt.ws.send(JSON.stringify({ cmd: 'set_param', slot: d.slot, key: d.key, value: d.value })); } catch (e) {}\n return;\n }\n if (d.type !== 'in') return;", 1), # 6. rewire start: noi fx vao masterFxGain (khong disconnect dry) (" try { masterBus.outputAnalyser.disconnect(masterBus.output); } catch (e) {}\n masterBus.outputAnalyser.connect(fxRt.node);\n fxRt.node.connect(masterBus.output);\n fxRt.chainKey = ck;", " // Crossfade activation: KHONG disconnect dry path — outputAnalyser van noi\n // masterFxDryGain -> pdcDelay -> output (gain 1); nhanh fx noi masterFxGain\n // (gain 0) -> output. Tin hieu 'started' tu worklet -> ramp 2 gain -> switch\n // muot, het click hard-rewire.\n try { masterBus.outputAnalyser.disconnect(masterBus.output); } catch (e) {}\n masterBus.outputAnalyser.connect(fxRt.node);\n fxRt.node.connect(masterBus.masterFxGain);\n fxRt.chainKey = ck;", 1), # 7. them ham fxRtCrossfade sau fxRtApplyPdc (" try { bus.pdcDelay.delayTime.setTargetAtTime(l / (ctx.sampleRate || 44100), ctx.currentTime, 0.05); } catch (e) {}\n}", " try { bus.pdcDelay.delayTime.setTargetAtTime(l / (ctx.sampleRate || 44100), ctx.currentTime, 0.05); } catch (e) {}\n}\n\n// Crossfade master-FX switch: ramp masterFxGain/masterFxDryGain thay vi\n// hard-rewire (click). Hai gain nay duoc tao trong initMasterBus.\nfunction fxRtCrossfade(toFx, token) {\n const ctx = getAudioContext();\n const bus = masterBus;\n if (!ctx || !bus || !bus.masterFxGain || !bus.masterFxDryGain) return;\n if (token !== undefined && token !== fxRt.startToken) return;\n const tc = 0.025;\n try {\n bus.masterFxGain.gain.setTargetAtTime(toFx ? 1 : 0, ctx.currentTime, tc);\n bus.masterFxDryGain.gain.setTargetAtTime(toFx ? 0 : 1, ctx.currentTime, tc);\n } catch (e) {}\n}", 1), # 8. fxRtStop: crossfade deactivation thay hard-switch; disconnect tre 180ms (" if (!fxRt.active && !fxRt.ws && !fxRt.node && !fxRt.sessionId) return;\n try {\n if (fxRt.node) {\n fxRt.node.disconnect();\n if (masterBus && masterBus.outputAnalyser && masterBus.output) {\n try { masterBus.outputAnalyser.disconnect(masterBus.output); } catch (e) {}\n try { masterBus.outputAnalyser.connect(masterBus.output); } catch (e) {}\n }\n fxRt.node.port.onmessage = null;\n fxRt.node = null;\n }\n } catch (e) {}", " if (!fxRt.active && !fxRt.ws && !fxRt.node && !fxRt.sessionId) return;\n let _fxStopNode = null;\n try {\n if (fxRt.node) {\n _fxStopNode = fxRt.node;\n if (masterBus && masterBus.masterFxGain && masterBus.masterFxDryGain) {\n // Crossfade deactivation: ramp dry 0->1 / fx 1->0 roi MOI disconnect\n // (tranh click hard-switch nguoc). Dry path da noi san qua\n // masterFxDryGain — khong can reconnect outputAnalyser->output. PDC giam\n // ve 0 sau khi dry chiem cho (fx silent).\n try { fxRtCrossfade(false); } catch (e) {}\n setTimeout(() => {\n try { _fxStopNode.disconnect(); } catch (e) {}\n _fxStopNode.port.onmessage = null;\n if (fxRt.node === _fxStopNode) fxRt.node = null;\n fxRt.fadedIn = false;\n fxRtApplyPdc();\n }, 180);\n } else {\n // Fallback graph cu (khong co crossfade gains) — hard switch nhu cu.\n try { _fxStopNode.disconnect(); } catch (e) {}\n if (masterBus && masterBus.outputAnalyser && masterBus.output) {\n try { masterBus.outputAnalyser.connect(masterBus.output); } catch (e) {}\n }\n _fxStopNode.port.onmessage = null;\n if (fxRt.node === _fxStopNode) fxRt.node = null;\n }\n }\n } catch (e) {}", 1), # 9. fxRtStop tail: PDC da xu ly trong setTimeout crossfade (hoac ngay neu khong co node) (" fxRt.chainKey = null;\n fxRt.latencyTotal = 0;\n fxRtApplyPdc();\n if (sid) {", " fxRt.chainKey = null;\n fxRt.latencyTotal = 0;\n // PDC giam ve 0 sau khi dry chiem cho (trong setTimeout cua nhanh crossfade)\n // hoac ngay neu khong co node — khong goi tai day de tranh gap 80ms.\n if (!_fxStopNode) fxRtApplyPdc();\n if (sid) {", 1), # 10. bump cache-buster (2 noi: master + per-track) ("sf-fx-realtime.js?v=202608241030", "sf-fx-realtime.js?v=202608241200", None), ] patch(appjsx, A) # ── sf-fx-realtime.js ─────────────────────────────────────────────────────── wl = 'app/static/js/sf-fx-realtime.js' W = [ (" this.hasStarted = false; // đã phát block đầu chưa — phân biệt FILL khởi đầu vs REBUFFER sau underrun", " this.hasStarted = false; // đã phát block đầu chưa — phân biệt FILL khởi đầu vs REBUFFER sau underrun\n this.FADE = 512; // fade-in 512 mẫu (~11ms @48k) khi bắt đầu phát/resume — hết click\n this.outGain = 0; // gain phát hiện tại (0→1 trong fade)\n this.fadeLeft = 0; // số mẫu còn lại của fade", 1), (" if (this.outQueue.length >= need) {\n this.playing = true;\n this.hasStarted = true;\n } else {", " if (this.outQueue.length >= need) {\n this.playing = true;\n this.hasStarted = true;\n // Fade-in mượt (hết click lúc FILL xong / resume sau underrun); báo\n // main thread để crossfade dry→fx đúng thời điểm.\n this.outGain = 0;\n this.fadeLeft = this.FADE;\n this.port.postMessage({ type: 'started' });\n } else {", 1), (" const blk = this.outQueue[0];\n outL[i] = blk.l[this.outPos];\n if (outR) outR[i] = blk.r[this.outPos];", " const blk = this.outQueue[0];\n if (this.fadeLeft > 0) {\n this.outGain = Math.min(1, this.outGain + 1 / this.FADE);\n this.fadeLeft--;\n }\n outL[i] = blk.l[this.outPos] * this.outGain;\n if (outR) outR[i] = blk.r[this.outPos] * this.outGain;", 1), ] patch(wl, W) print('ALL OK')