fix(playback): het note chong am echo o bass - KHONG huy note-off cu cung pitch (Bug 2 huy mui lam note cu khong bao gio tat); giu neu note-off cu bam truoc note-on moi, doi ve ngay truoc note-on neu overlap; sync 6 ban JS + index.html ?v=202608181900
This commit is contained in:
+22
-9
@@ -118,25 +118,38 @@ const ensureSonicInstrument = (ctx) => {
|
||||
// câm. Map pending theo key channel:pitch — schedule NOTE_OFF mới cùng key →
|
||||
// hủy NOTE_OFF cũ (retrigger đúng, note mới ngân đủ duration).
|
||||
const pendingNoteOffMap = {};
|
||||
const scheduleNoteOff = (key, fn, delayMs) => {
|
||||
// V18: note-off cũ cùng key KHÔNG bị hủy mù — hủy mù làm note cùng pitch lặp
|
||||
// (bass: root note chạy liên tục) không bao giờ nhận note-off -> kêu dài chồng
|
||||
// âm echo, không decay. So sánh thời điểm: note-off cũ bắn TRƯỚC note-on mới →
|
||||
// giữ nguyên (decay đúng hạn). Note-off cũ bắn SAU note-on mới (overlap — Bug 2
|
||||
// cũ) → dời về NGAY TRƯỚC note-on mới: note cũ decay sạch, note mới ngân đủ.
|
||||
const scheduleNoteOff = (key, fn, noteOnDelayMs, durMs) => {
|
||||
const offDelay = noteOnDelayMs + (durMs || 1000) + 30;
|
||||
const prev = pendingNoteOffMap[key];
|
||||
if (prev) { try { clearTimeout(prev); } catch (e) {} }
|
||||
if (prev && prev.offDelay > noteOnDelayMs) {
|
||||
try { clearTimeout(prev.timer); } catch (e) {}
|
||||
const t = setTimeout(function () {
|
||||
if (pendingNoteOffMap[key] && pendingNoteOffMap[key].timer === t) delete pendingNoteOffMap[key];
|
||||
prev.fn();
|
||||
}, Math.max(0, noteOnDelayMs - 5));
|
||||
}
|
||||
const timer = setTimeout(function () {
|
||||
if (pendingNoteOffMap[key] === timer) delete pendingNoteOffMap[key];
|
||||
if (pendingNoteOffMap[key] && pendingNoteOffMap[key].timer === timer) delete pendingNoteOffMap[key];
|
||||
fn();
|
||||
}, delayMs);
|
||||
pendingNoteOffMap[key] = timer;
|
||||
}, offDelay);
|
||||
pendingNoteOffMap[key] = { timer: timer, fn: fn, offDelay: offDelay };
|
||||
return timer;
|
||||
};
|
||||
const clearAllPendingNoteOffs = () => {
|
||||
Object.keys(pendingNoteOffMap).forEach(function (k) {
|
||||
try { clearTimeout(pendingNoteOffMap[k]); } catch (e) {}
|
||||
const entry = pendingNoteOffMap[k];
|
||||
if (entry) { try { clearTimeout(entry.timer); } catch (e) {} }
|
||||
delete pendingNoteOffMap[k];
|
||||
});
|
||||
};
|
||||
const fireCarlaNote = (ch, pitch, vel, delay, durMs) => {
|
||||
setTimeout(function () { window.SonicCarlaMidi.noteOn(ch, pitch, vel); }, delay);
|
||||
scheduleNoteOff('C:' + ch + ':' + pitch, function () { window.SonicCarlaMidi.noteOff(ch, pitch); }, delay + durMs + 30);
|
||||
scheduleNoteOff('C:' + ch + ':' + pitch, function () { window.SonicCarlaMidi.noteOff(ch, pitch); }, delay, durMs);
|
||||
};
|
||||
// ⚠️ FIX (Bug 2): Carla mở ASYNC lúc play (ensureCarlaForPlayback) — nốt gửi
|
||||
// trước khi OSC engine bind cổng bị mất → item câm dù keybed kêu. Nốt khi
|
||||
@@ -22681,7 +22694,7 @@ const App = () => {
|
||||
if (epoch !== bridgeNoteEpochRef.current) return;
|
||||
if (!guardPlay()) return;
|
||||
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: trkId, pitch: pitch || 60, velocity: 0, percussion: isPerc }); } catch (e) {}
|
||||
}, delayMs + (durMs || 1000) + 30);
|
||||
}, delayMs, durMs || 1000);
|
||||
return;
|
||||
}
|
||||
// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
||||
@@ -23215,7 +23228,7 @@ const App = () => {
|
||||
const carlaCh = (synthEngine && synthEngine.midi_channel !== undefined) ? synthEngine.midi_channel : mainCh;
|
||||
const carlaPitch = note.pitch || 60;
|
||||
setTimeout(function () { window.SonicCarlaMidi.noteOn(carlaCh, carlaPitch, carlaVel); }, carlaDelay);
|
||||
scheduleNoteOff('C:' + carlaCh + ':' + carlaPitch, function () { window.SonicCarlaMidi.noteOff(carlaCh, carlaPitch); }, carlaDelay + durMs + 30);
|
||||
scheduleNoteOff('C:' + carlaCh + ':' + carlaPitch, function () { window.SonicCarlaMidi.noteOff(carlaCh, carlaPitch); }, carlaDelay, durMs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ const ensureSonicInstrument=ctx=>{try{if(!window.SonicSF||!window.SonicSF.select
|
||||
// tiếp (bar liền nhau cùng hợp âm, piano roll note chồng) -> cắt note mới →
|
||||
// câm. Map pending theo key channel:pitch — schedule NOTE_OFF mới cùng key →
|
||||
// hủy NOTE_OFF cũ (retrigger đúng, note mới ngân đủ duration).
|
||||
const pendingNoteOffMap={};const scheduleNoteOff=(key,fn,delayMs)=>{const prev=pendingNoteOffMap[key];if(prev){try{clearTimeout(prev);}catch(e){}}const timer=setTimeout(function(){if(pendingNoteOffMap[key]===timer)delete pendingNoteOffMap[key];fn();},delayMs);pendingNoteOffMap[key]=timer;return timer;};const clearAllPendingNoteOffs=()=>{Object.keys(pendingNoteOffMap).forEach(function(k){try{clearTimeout(pendingNoteOffMap[k]);}catch(e){}delete pendingNoteOffMap[k];});};const fireCarlaNote=(ch,pitch,vel,delay,durMs)=>{setTimeout(function(){window.SonicCarlaMidi.noteOn(ch,pitch,vel);},delay);scheduleNoteOff('C:'+ch+':'+pitch,function(){window.SonicCarlaMidi.noteOff(ch,pitch);},delay+durMs+30);};// ⚠️ FIX (Bug 2): Carla mở ASYNC lúc play (ensureCarlaForPlayback) — nốt gửi
|
||||
const pendingNoteOffMap={};const scheduleNoteOff=(key,fn,noteOnDelayMs,durMs)=>{const offDelay=noteOnDelayMs+(durMs||1000)+30;const prev=pendingNoteOffMap[key];if(prev&&prev.offDelay>noteOnDelayMs){try{clearTimeout(prev.timer);}catch(e){}const t=setTimeout(function(){if(pendingNoteOffMap[key]&&pendingNoteOffMap[key].timer===t)delete pendingNoteOffMap[key];prev.fn();},Math.max(0,noteOnDelayMs-5));}const timer=setTimeout(function(){if(pendingNoteOffMap[key]&&pendingNoteOffMap[key].timer===timer)delete pendingNoteOffMap[key];fn();},offDelay);pendingNoteOffMap[key]={timer:timer,fn:fn,offDelay:offDelay};return timer;};const clearAllPendingNoteOffs=()=>{Object.keys(pendingNoteOffMap).forEach(function(k){const entry=pendingNoteOffMap[k];if(entry){try{clearTimeout(entry.timer);}catch(e){}}delete pendingNoteOffMap[k];});};const fireCarlaNote=(ch,pitch,vel,delay,durMs)=>{setTimeout(function(){window.SonicCarlaMidi.noteOn(ch,pitch,vel);},delay);scheduleNoteOff('C:'+ch+':'+pitch,function(){window.SonicCarlaMidi.noteOff(ch,pitch);},delay,durMs);};// ⚠️ FIX (Bug 2): Carla mở ASYNC lúc play (ensureCarlaForPlayback) — nốt gửi
|
||||
// trước khi OSC engine bind cổng bị mất → item câm dù keybed kêu. Nốt khi
|
||||
// chưa ready được nhét queue, flush khi bridge OSC ready (giữ đúng thứ tự).
|
||||
const flushCarlaNoteQueue=()=>{const q=window.__carlaNoteQueue||[];if(!q.length)return;window.__carlaNoteQueue=[];q.forEach(item=>{const remaining=item.delay-(Date.now()-item.scheduledAt);if(remaining<=0)fireCarlaNote(item.ch,item.pitch,item.vel,0,item.durMs);else fireCarlaNote(item.ch,item.pitch,item.vel,remaining,item.durMs);});};const scheduleCarlaNote=(synthEngine,channel,pitch,velocity,startWallTime,durMs)=>{try{if(!window.SonicCarlaMidi||!window.SonicCarlaMidi.shouldRoutePlayback(synthEngine))return;const ctx=getAudioContext();const delay=Math.max(0,(startWallTime-ctx.currentTime)*1000);const carlaVel=Math.round((velocity||0.8)*127);const carlaCh=synthEngine&&synthEngine.midi_channel!==undefined?synthEngine.midi_channel:channel||0;// Chẩn đoán: note MIDI item → Carla (bật console.log để verify route)
|
||||
@@ -1607,7 +1607,7 @@ const guardPlay=startTime!=null?function(){return isPlayingRef.current||subTabsR
|
||||
const isPerc=!!(synthEngine&&synthEngine.soundfont_bank===128);// D8: track đổi instrument → gửi CC0/CC32 (bank THẬT từ synthEngine) +
|
||||
// program change qua bridge (A12) — helper dedupe theo track+bank+program.
|
||||
if(program!==undefined&&program!==null){try{if(window.__ensureBridgeProgram)window.__ensureBridgeProgram(trkId,program,synthEngine);}catch(e){}}// V11: epoch guard — hủy timer cũ khi stop/loop restart (xem ref).
|
||||
const epoch=bridgeNoteEpochRef.current;setTimeout(function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_ON',channel:trkId,pitch:pitch||60,velocity:velocity||0.8,percussion:isPerc});}catch(e){}},delayMs);scheduleNoteOff('B:'+trkId+':'+(pitch||60),function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_OFF',channel:trkId,pitch:pitch||60,velocity:0,percussion:isPerc});}catch(e){}},delayMs+(durMs||1000)+30);return;}// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
||||
const epoch=bridgeNoteEpochRef.current;setTimeout(function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_ON',channel:trkId,pitch:pitch||60,velocity:velocity||0.8,percussion:isPerc});}catch(e){}},delayMs);scheduleNoteOff('B:'+trkId+':'+(pitch||60),function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_OFF',channel:trkId,pitch:pitch||60,velocity:0,percussion:isPerc});}catch(e){}},delayMs,durMs||1000);return;}// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
||||
// FluidSynth GM sai âm — safety net cho mọi caller (ghost notes, per-note).
|
||||
if(isStandaloneSf()&&isVstTrackEngine(synthEngine)&&!(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive())){playNativeVstNote(track,pitch,velocity,durMs,startTime,track?track.id:'global');return;}window.SonicSF.playNote(pitch,velocity,durMs,startTime,program,destNode,ch,synthEngine);};const startTrackPlayback=offsetTime=>{const context=getAudioContext();_nativePlayEpoch++;restorePendingFadeSession();// new play — vô hiệu doHardStop đang fade (play trong cửa sổ 80ms)
|
||||
// D2: bridge active -> báo transport PLAY (bridge flush note-off cũ + đồng
|
||||
@@ -1704,7 +1704,7 @@ const routeCarla=shouldRouteCarla(synthEngine);if(isStandaloneSf()&&!routeCarla&
|
||||
// Carla — không play FluidSynth GM sai âm chồng lên)
|
||||
const routeToCarla=!!(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(synthEngine));if(!routeToCarla&&window.SonicSF){scheduleMidiNoteDispatch(track,note.pitch||60,note.velocity||0.8,durMs,scheduledTime,instrumentProgram,destNode,mainCh,synthEngine);}// MIDI items → Carla (track VSTi + Carla local): phát VSTi realtime
|
||||
// (schedule theo audio clock bằng setTimeout — preview, timing gần đúng).
|
||||
if(routeToCarla){const carlaDelay=Math.max(0,(scheduledTime-context.currentTime)*1000);const carlaVel=Math.round((note.velocity||0.8)*127);const carlaCh=synthEngine&&synthEngine.midi_channel!==undefined?synthEngine.midi_channel:mainCh;const carlaPitch=note.pitch||60;setTimeout(function(){window.SonicCarlaMidi.noteOn(carlaCh,carlaPitch,carlaVel);},carlaDelay);scheduleNoteOff('C:'+carlaCh+':'+carlaPitch,function(){window.SonicCarlaMidi.noteOff(carlaCh,carlaPitch);},carlaDelay+durMs+30);}}});// Play ghost notes from active tracks
|
||||
if(routeToCarla){const carlaDelay=Math.max(0,(scheduledTime-context.currentTime)*1000);const carlaVel=Math.round((note.velocity||0.8)*127);const carlaCh=synthEngine&&synthEngine.midi_channel!==undefined?synthEngine.midi_channel:mainCh;const carlaPitch=note.pitch||60;setTimeout(function(){window.SonicCarlaMidi.noteOn(carlaCh,carlaPitch,carlaVel);},carlaDelay);scheduleNoteOff('C:'+carlaCh+':'+carlaPitch,function(){window.SonicCarlaMidi.noteOff(carlaCh,carlaPitch);},carlaDelay,durMs);}}});// Play ghost notes from active tracks
|
||||
var ghostLayers=st.ghostPlayLayers||[];ghostLayers.forEach(function(layer){var ghostTrack=allTracks.find(function(t){return t.id===layer.trackId;});var ghostProg=layer.instrumentProgram!==undefined?layer.instrumentProgram:ghostTrack?ghostTrack.instrumentProgram:undefined;var ghostSynth=layer.synthEngine||(ghostTrack?ghostTrack.synth_engine:undefined);if(ghostProg===undefined&&!ghostSynth)return;var ghostDest=getOrCreateTrackNode(ghostTrack,context);var ghostCh=ghostTrack?assignTrackMidiChannel(ghostTrack,allTracks):0;layer.notes.forEach(function(note){var beat=note.start_beat||0;var dur=note.duration_beats||1;var startSec=beat*secondsPerBeat;var durSec=dur*secondsPerBeat;if(startSec+durSec>offsetSeconds){var effStart=Math.max(0,startSec-offsetSeconds);var effDur=durSec-Math.max(0,offsetSeconds-startSec);var schedTime=startWallTime+effStart;var durMs=effDur*1000;// Ghost note → Carla bridge khi ghost track VSTi (chỉ route Carla)
|
||||
var gRouteCarla=!!(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(ghostSynth));if(!gRouteCarla&&window.SonicSF){scheduleMidiNoteDispatch(ghostTrack,note.pitch||60,note.velocity||0.8,durMs,schedTime,ghostProg,ghostDest,ghostCh,ghostSynth);}if(gRouteCarla)scheduleCarlaNote(ghostSynth,ghostCh,note.pitch||60,note.velocity||0.8,schedTime,durMs);}});});};const handlePlayPause=()=>{console.log('[Play] click activeTab=',activeTab,'isPlaying=',isPlaying,'subPlaying=',subTabs.filter(s=>s.isPlaying).length);if(activeTab!=='main'&&!activeTab.startsWith('session_')){// Sub-tab playback transport
|
||||
const st=subTabs.find(s=>s.id===activeTab);if(!st||!st.buffer&&st.type!=='PIANO_ROLL')return;// MAIN/SECTION đang play (ngầm — MIDI section/main còn kêu sau khi mở
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<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/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608181840" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608181900" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user