fix: lag khi ARM sau reload + câm sau vài lần nhấn MIDI - dedup loadSoundFont (1 font=1 sfload, tránh cạn heap 256MB) + preload instrument khi mở/khôi phục dự án
This commit is contained in:
@@ -4627,6 +4627,23 @@ const ProfileModal = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const preloadTrackInstruments = async (tracks) => {
|
||||||
|
// Preload each track's soundfont before the user plays — otherwise the first
|
||||||
|
// MIDI key after reload triggers a lazy font load and the note sounds late.
|
||||||
|
if (!window.SonicSF || !window.SonicSF.selectInstrument) return;
|
||||||
|
for (const t of (tracks || [])) {
|
||||||
|
const se = t.synth_engine;
|
||||||
|
const sfId = (se && se.soundfont_id) || t.soundfont_id;
|
||||||
|
if (!sfId) continue;
|
||||||
|
const bank = se ? (se.soundfont_bank !== undefined ? se.soundfont_bank : 0) : (t.soundfont_bank !== undefined ? t.soundfont_bank : 0);
|
||||||
|
const prog = se ? (se.soundfont_program !== undefined ? se.soundfont_program : 0) : (t.soundfont_program !== undefined ? t.soundfont_program : 0);
|
||||||
|
var ch = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(t, tracks) : (t.midiChannel !== undefined ? t.midiChannel : 0);
|
||||||
|
try {
|
||||||
|
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpenProject = async (projectId, projectName) => {
|
const handleOpenProject = async (projectId, projectName) => {
|
||||||
setAppWarningModal({
|
setAppWarningModal({
|
||||||
title: "Mở dự án",
|
title: "Mở dự án",
|
||||||
@@ -4685,6 +4702,7 @@ const ProfileModal = ({
|
|||||||
}
|
}
|
||||||
setTracks(restoredTracks);
|
setTracks(restoredTracks);
|
||||||
loadAudioBuffersForTracks(restoredTracks).catch(function(err) { console.warn('loadAudioBuffersForTracks error:', err); });
|
loadAudioBuffersForTracks(restoredTracks).catch(function(err) { console.warn('loadAudioBuffersForTracks error:', err); });
|
||||||
|
preloadTrackInstruments(restoredTracks).catch(function(err) { console.warn('preloadTrackInstruments error:', err); });
|
||||||
setBpm(restoredBpm.toString());
|
setBpm(restoredBpm.toString());
|
||||||
setSelectedTrackId(restoredTracks[0]?.id || '1');
|
setSelectedTrackId(restoredTracks[0]?.id || '1');
|
||||||
setProjectName(proj.name);
|
setProjectName(proj.name);
|
||||||
@@ -12110,6 +12128,7 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
setTracks(restoredTracks);
|
setTracks(restoredTracks);
|
||||||
loadAudioBuffersForTracks(restoredTracks).catch(function(err) { console.warn('loadAudioBuffersForTracks error:', err); });
|
loadAudioBuffersForTracks(restoredTracks).catch(function(err) { console.warn('loadAudioBuffersForTracks error:', err); });
|
||||||
|
preloadTrackInstruments(restoredTracks).catch(function(err) { console.warn('preloadTrackInstruments error:', err); });
|
||||||
setBpm(restoredBpm.toString());
|
setBpm(restoredBpm.toString());
|
||||||
setSelectedTrackId(restoredTracks[0]?.id || '1');
|
setSelectedTrackId(restoredTracks[0]?.id || '1');
|
||||||
setProjectName(lastName);
|
setProjectName(lastName);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -23,6 +23,8 @@
|
|||||||
let _activeOscillators = {};
|
let _activeOscillators = {};
|
||||||
let _gainNode = null;
|
let _gainNode = null;
|
||||||
let _scheduledNotes = [];
|
let _scheduledNotes = [];
|
||||||
|
let _loadPromises = {};
|
||||||
|
let _sfloadSeq = 0;
|
||||||
|
|
||||||
const getCtx = function () {
|
const getCtx = function () {
|
||||||
if (_audioCtx) {
|
if (_audioCtx) {
|
||||||
@@ -186,7 +188,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_tryLoadSFL: function (buf, ext) {
|
_tryLoadSFL: function (buf, ext) {
|
||||||
var fname = '/' + ext + '_' + Date.now();
|
var fname = '/' + ext + '_' + (++_sfloadSeq) + '_' + Date.now();
|
||||||
try { _fluidModule.FS.unlink(fname); } catch (e) {}
|
try { _fluidModule.FS.unlink(fname); } catch (e) {}
|
||||||
_fluidModule.FS.writeFile(fname, new Uint8Array(buf));
|
_fluidModule.FS.writeFile(fname, new Uint8Array(buf));
|
||||||
var cPath = this._allocCStr(fname);
|
var cPath = this._allocCStr(fname);
|
||||||
@@ -203,6 +205,18 @@
|
|||||||
_currentSfId = sfId;
|
_currentSfId = sfId;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Deduplicate concurrent loads: rapid key presses (or several armed
|
||||||
|
// tracks) all call loadSoundFont for the same font before the first
|
||||||
|
// load resolves. Without this, the same soundfont is sfload'd several
|
||||||
|
// times (handles 1,2,3,4…) — wasting the 256MB WASM heap and stalling
|
||||||
|
// notes until each load finishes (audible lag, then silence).
|
||||||
|
if (!_loadPromises[sfId]) {
|
||||||
|
_loadPromises[sfId] = this._doLoadSoundFont(sfId);
|
||||||
|
}
|
||||||
|
return _loadPromises[sfId];
|
||||||
|
},
|
||||||
|
|
||||||
|
_doLoadSoundFont: async function (sfId) {
|
||||||
try {
|
try {
|
||||||
var cache = window.SonicSFStorage;
|
var cache = window.SonicSFStorage;
|
||||||
var buf = cache ? await cache.getBuffer(sfId) : null;
|
var buf = cache ? await cache.getBuffer(sfId) : null;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<script src="/static/js/services/audioEngine.js?v=202607271016"></script>
|
<script src="/static/js/services/audioEngine.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/storage.js?v=202607271016"></script>
|
<script src="/static/js/services/storage.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
|
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/soundfontPlayer.js?v=202608031030"></script>
|
<script src="/static/js/services/soundfontPlayer.js?v=202608031040"></script>
|
||||||
<script src="/static/js/services/aiGateway.js?v=202607271016"></script>
|
<script src="/static/js/services/aiGateway.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/dawCommandDispatcher.js?v=202607271016"></script>
|
<script src="/static/js/services/dawCommandDispatcher.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/pianoRollTabService.js?v=202607272044"></script>
|
<script src="/static/js/services/pianoRollTabService.js?v=202607272044"></script>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608022001" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608031040" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1204,3 +1204,8 @@
|
|||||||
- **Tóm tắt thay đổi:** `_playNoteFluid` trước đây ưu tiên state channel (`_channels[ch].sfId`) làm nguồn instrument cho note → khi ARM nhiều track hoặc track đổi instrument qua dropdown nhanh, note bị phát theo instrument cũ/khác đang "dính" trên channel (leftover state hoặc 2 track trùng channel) → sai instrument từng track. Sửa: `synth_engine` của track là nguồn quyết định; channel state chỉ là cache (chỉ dùng khi note không có engine). Khi note mang engine khác với channel đang giữ, tự động `program_select` lại đúng instrument trước `noteon`; vẫn lazy-load soundfont nếu chưa nạp. Bump version cache `soundfontPlayer.js` trong `index.html`.
|
- **Tóm tắt thay đổi:** `_playNoteFluid` trước đây ưu tiên state channel (`_channels[ch].sfId`) làm nguồn instrument cho note → khi ARM nhiều track hoặc track đổi instrument qua dropdown nhanh, note bị phát theo instrument cũ/khác đang "dính" trên channel (leftover state hoặc 2 track trùng channel) → sai instrument từng track. Sửa: `synth_engine` của track là nguồn quyết định; channel state chỉ là cache (chỉ dùng khi note không có engine). Khi note mang engine khác với channel đang giữ, tự động `program_select` lại đúng instrument trước `noteon`; vẫn lazy-load soundfont nếu chưa nạp. Bump version cache `soundfontPlayer.js` trong `index.html`.
|
||||||
- **Các file ảnh hưởng:** `app/static/js/services/soundfontPlayer.js`, `app/templates/index.html`
|
- **Các file ảnh hưởng:** `app/static/js/services/soundfontPlayer.js`, `app/templates/index.html`
|
||||||
- **Ghi chú/Test (nếu có):** Harness `node /tmp/kilo/test_multi_track.js` (mô phỏng routing ARM như app.jsx): (A) 3 track 3 instrument distinct channel → noteon đúng, không re-select thừa; (B) 3 track quick-pick (không midiChannel, channel đang giữ instrument cũ) → mỗi track re-select đúng Q1/Q2/Q3 trước noteon; (C) 2 track trùng channel 0 → track 2 re-select program 40 trước noteon. Harness `test_sonicsf_fix.js` (single-track S1-S5) vẫn ALL PASSED. Hard refresh trình duyệt để nạp bản mới.
|
- **Ghi chú/Test (nếu có):** Harness `node /tmp/kilo/test_multi_track.js` (mô phỏng routing ARM như app.jsx): (A) 3 track 3 instrument distinct channel → noteon đúng, không re-select thừa; (B) 3 track quick-pick (không midiChannel, channel đang giữ instrument cũ) → mỗi track re-select đúng Q1/Q2/Q3 trước noteon; (C) 2 track trùng channel 0 → track 2 re-select program 40 trước noteon. Harness `test_sonicsf_fix.js` (single-track S1-S5) vẫn ALL PASSED. Hard refresh trình duyệt để nạp bản mới.
|
||||||
|
|
||||||
|
### [2026-08-03 10:40] Task: Fix lag khi ARM sau reload + hết âm thanh sau vài lần nhấn MIDI
|
||||||
|
- **Tóm tắt thay đổi:** (1) **Dedup load soundfont**: `loadSoundFont` trước đây không chặn các lệnh gọi đồng thời — nhấn phím MIDI liên tục (hoặc nhiều track ARM) kích hoạt lazy-load cùng lúc → SGM-V2.01 bị `sfload` 4 lần (handle 1,2,3,4 trong log), mỗi bản ~52MB trong heap WASM 256MB → cạn bộ nhớ, các lần load sau fail → câm sau vài lần nhấn + mỗi note chờ load (lag). Thêm `_loadPromises[sfId]` dedup (1 font = 1 lần load, cache cả kết quả fail) + `_sfloadSeq` cho filename temp duy nhất tránh trùng tên khi load 2 font khác nhau đồng thời. (2) **Preload instrument khi mở/khôi phục dự án**: thêm `preloadTrackInstruments(tracks)` gọi `selectInstrument` cho từng track có soundfont ngay sau khi `setTracks` trong `handleOpenProject` và `restoreLastSessionProject` → nhấn MIDI key đầu tiên không còn lag (font đã nạp sẵn).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/services/soundfontPlayer.js`, `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (build lại), `app/templates/index.html`
|
||||||
|
- **Ghi chú/Test (nếu có):** `npm run build` (babel) OK, bundle chứa `preloadTrackInstruments`. Harness `node /tmp/kilo/test_dedup.js`: 8 lần `loadSoundFont` cùng 1 font đồng thời → chỉ 1 `sfload`, 1 lần đọc cache; load lại font đã nạp → 0 sfload; 2 font khác nhau đồng thời → đúng 2 sfload — ALL PASSED. `test_sonicsf_fix.js` + `test_multi_track.js` vẫn ALL PASSED. Hard refresh trình duyệt.
|
||||||
|
|||||||
Reference in New Issue
Block a user