diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx
index e9bd63f..2e7b752 100644
--- a/app/static/js/app.jsx
+++ b/app/static/js/app.jsx
@@ -13657,6 +13657,7 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
synthListRef.current = synthList;
const synthInstRef = React.useRef(null);
synthInstRef.current = synthInst;
+ const btnSynthRef = React.useRef(null);
const [treeWidth, setTreeWidth] = React.useState(function() {
var saved = localStorage.getItem('studio_media_explorer_tree_width');
return saved ? parseInt(saved) : 176;
@@ -14272,7 +14273,13 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
const delayMs = Math.max(0, (at - ctx.currentTime) * 1000);
const pitch = note.pitch || 60;
const vel = note.velocity != null ? note.velocity : 0.8;
- window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: ch, pitch: pitch, velocity: vel, percussion: isPerc, live: true });
+ // NOTE_ON cũng delay theo beat (giống scheduleMidiNoteDispatch) — gửi
+ // ngay t=0 khi needLoad=true sẽ mất note vì SF2 chưa load xong trong
+ // bridge (uiWorker async bên C++).
+ setTimeout(() => {
+ if (selectTokenRef.current !== (o.token || selectTokenRef.current)) return;
+ window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: ch, pitch: pitch, velocity: vel, percussion: isPerc, live: true });
+ }, delayMs);
setTimeout(() => {
if (selectTokenRef.current !== (o.token || selectTokenRef.current)) return;
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: ch, pitch: pitch, velocity: 0, percussion: isPerc, live: true });
@@ -15145,12 +15152,18 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
Auto-Play
-