FIX: sửa lỗi hiển thị animation của VU meter
This commit is contained in:
+91
-6
@@ -13900,6 +13900,12 @@ const App = () => {
|
||||
selectedItemIdsRef.current = selectedItemIds;
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
// Ref theo dõi isPlaying — setTimeout VU trigger (main-midi delayed + section
|
||||
// delayed) check ref này trước khi fire: stop → isPlaying=false → setTimeout
|
||||
// sót không fire → VU không nhảy sau khi âm đã dừng (user: VU vẫn animation
|
||||
// khi hết âm).
|
||||
const isPlayingRef = useRef(false);
|
||||
isPlayingRef.current = isPlaying;
|
||||
const [selectionStart, setSelectionStart] = useState(null);
|
||||
const [selectionEnd, setSelectionEnd] = useState(null);
|
||||
const [selectionFollowsTempo, setSelectionFollowsTempo] = useState(true);
|
||||
@@ -14988,6 +14994,14 @@ const App = () => {
|
||||
const velFactor = typeof velocity === 'number' ? (velocity > 1 ? velocity / 127 : velocity) : 0.8;
|
||||
const peak = Math.min(1.0, Math.max(0.15, velFactor));
|
||||
midiVuActivityRef.current[trackId] = peak;
|
||||
// Log nguồn trigger (rate-limited 1/20 fire): xác định ai còn fire VU khi
|
||||
// âm đã ngừng (user: VU vẫn animation sau khi hết âm).
|
||||
try {
|
||||
const _tf = (window.__trigFrame = (window.__trigFrame || 0) + 1);
|
||||
if (_tf % 20 === 1) {
|
||||
console.log('[VUTrig]', trackId, 'vel=', velocity, 'peak=', peak.toFixed(2), 'play=', isPlaying, 'tab=', activeTabRef.current);
|
||||
}
|
||||
} catch (e) { }
|
||||
};
|
||||
window.triggerMidiVuActivity = triggerMidiVuActivity;
|
||||
|
||||
@@ -19349,6 +19363,9 @@ const App = () => {
|
||||
);
|
||||
// Trigger VU meter flash when the note starts playing
|
||||
setTimeout(() => {
|
||||
// ⚠️ Guard: stop → setTimeout sót không được fire (VU nhảy
|
||||
// sau khi âm đã dừng — user bug 05:45).
|
||||
if (!isPlayingRef.current) return;
|
||||
if (window.triggerMidiVuActivity) {
|
||||
window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
|
||||
}
|
||||
@@ -19480,6 +19497,20 @@ const App = () => {
|
||||
subCh,
|
||||
subTrack.synth_engine
|
||||
);
|
||||
// Trigger VU meter flash đúng nhịp (cả subTrack — VU
|
||||
// section tab + parent track — VU MAIN): MIDI section đi
|
||||
// SF → masterBus.input (không qua sub-node analyser) →
|
||||
// VU track chỉ nhảy qua midiVuActivity. (Bị mất khi
|
||||
// commit gộp — khôi phục fix 00:30/02:00.)
|
||||
setTimeout(() => {
|
||||
// ⚠️ Guard: stop → setTimeout sót không được fire (VU
|
||||
// nhảy sau khi âm đã dừng — user bug 05:45).
|
||||
if (!isPlayingRef.current) return;
|
||||
if (window.triggerMidiVuActivity) {
|
||||
window.triggerMidiVuActivity(subTrack.id, note.velocity || 0.8);
|
||||
window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
|
||||
}
|
||||
}, delay * 1000);
|
||||
} else {
|
||||
const remainingDurMs = (notePlayEndMain - offsetTime) * 1000;
|
||||
window.SonicSF.playNote(
|
||||
@@ -19492,16 +19523,12 @@ const App = () => {
|
||||
subCh,
|
||||
subTrack.synth_engine
|
||||
);
|
||||
}
|
||||
// Trigger VU meter flash đúng nhịp (giống main-track midi 19066)
|
||||
// Cả subTrack (VU section tab) + parent track (VU MAIN — section
|
||||
// item đang play) — midiVuActivity keyed theo track id → trigger
|
||||
// cả 2 để VU main nhảy theo section.
|
||||
if (window.triggerMidiVuActivity) {
|
||||
window.triggerMidiVuActivity(subTrack.id, note.velocity || 0.8);
|
||||
window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -24932,8 +24959,17 @@ STRICT CONSTRAINTS:
|
||||
const v = Math.abs(data[i] - 128) / 128;
|
||||
if (v > peak) peak = v;
|
||||
}
|
||||
// Deadband: bỏ noise nền (log 06:00 — master peak 0.008 liên tục dù
|
||||
// không âm → master VU nhảy nhẹ mãi)
|
||||
if (peak < 0.01) peak = 0;
|
||||
setMasterVU(peak);
|
||||
setMasterMeterPeak(prev => Math.max(prev * 0.97, peak));
|
||||
// Log master VU khi đang nhảy (chẩn đoán 06:00 — VU nhảy ngoài vùng âm)
|
||||
if (peak > 0.001) {
|
||||
try {
|
||||
console.log('[VU-master] peak=', peak.toFixed(3));
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Track VU Meters
|
||||
@@ -24989,20 +25025,69 @@ STRICT CONSTRAINTS:
|
||||
}
|
||||
}
|
||||
|
||||
// MIDI THỰC: đọc từ SF output (âm post-FluidSynth) — thay velocity
|
||||
// trigger (GIẢ — fire lúc schedule — click playhead → re-schedule →
|
||||
// VU nhảy dù âm không ra — log 06:00: midi=0.98 nhưng master=0.008).
|
||||
// Track MIDI đi SF → masterBus (không qua node analyser) → tap tại SF
|
||||
// output gain.
|
||||
let sfAudioPeak = 0;
|
||||
let _sfTapped = false;
|
||||
if (audioPeak <= 0.001) {
|
||||
try {
|
||||
if (window.SonicSF && window.SonicSF.getOutputNode) {
|
||||
const sfNode = window.SonicSF.getOutputNode();
|
||||
if (sfNode) {
|
||||
if (!sfNode.__vuAnalyser) {
|
||||
const _sa = getAudioContext().createAnalyser();
|
||||
_sa.fftSize = 256;
|
||||
_sa.smoothingTimeConstant = 0.2;
|
||||
sfNode.connect(_sa);
|
||||
sfNode.__vuAnalyser = _sa;
|
||||
}
|
||||
_sfTapped = true;
|
||||
const d3 = new Uint8Array(128);
|
||||
sfNode.__vuAnalyser.getByteTimeDomainData(d3);
|
||||
for (let i = 0; i < d3.length; i++) {
|
||||
const v = Math.abs(d3[i] - 128) / 128;
|
||||
if (v > sfAudioPeak) sfAudioPeak = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
// midiVuActivityRef được set bởi triggerMidiVuActivity — cả khi play
|
||||
// MIDI item LẪN khi ARM + nhấn phím MIDI keyboard (preview) — nên VU
|
||||
// nhảy trong cả 2 trường hợp (không chặn bởi isPlaying).
|
||||
let midiPeak = isAudible ? (midiVuActivityRef.current[trackId] || 0) : 0;
|
||||
// SF analyser đã tap → CHỈ dùng âm THỰC (velocity trigger = giả — bỏ)
|
||||
if (_sfTapped) midiPeak = 0;
|
||||
if (midiPeak > 0) {
|
||||
midiVuActivityRef.current[trackId] = midiPeak * 0.90;
|
||||
// Decay nhanh hơn (0.72/frame ≈ tắt sau ~0.35s) — hết note → VU
|
||||
// dừng nhanh, không "vẫn diễn animation" khi không còn âm.
|
||||
midiVuActivityRef.current[trackId] = midiPeak * 0.72;
|
||||
if (midiVuActivityRef.current[trackId] < 0.01) {
|
||||
midiVuActivityRef.current[trackId] = 0;
|
||||
}
|
||||
}
|
||||
// Gộp âm MIDI THỰC vào audioPeak
|
||||
if (sfAudioPeak > audioPeak) audioPeak = sfAudioPeak;
|
||||
// Deadband: bỏ noise nền analyser (log 06:05 — audio=0.008 dù không
|
||||
// âm → VU hiển thị đoạn nhỏ "vẽ nhưng không animation") — hết âm →
|
||||
// VU tắt NGAY LẬP TỨC (user yêu cầu).
|
||||
if (audioPeak < 0.01) audioPeak = 0;
|
||||
|
||||
const peak = Math.max(audioPeak, midiPeak);
|
||||
const db = peak > 0 ? 20 * Math.log10(peak) : -60;
|
||||
|
||||
// Log chẩn đoán VU: CHỈ in khi peak > 0 (VU đang nhảy thật) — xác định
|
||||
// nguồn nào còn nhảy khi playhead ngoài vùng âm (user bug 06:00).
|
||||
if (peak > 0.001) {
|
||||
try {
|
||||
console.log('[VU]', trackId, 'audio=', audioPeak.toFixed(3), 'midi=', midiPeak.toFixed(3), 'key=', key);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
if (peak > 0.001) {
|
||||
if (key.endsWith('_mixer')) {
|
||||
drawMixerVuMeter(canvas, peak);
|
||||
|
||||
@@ -609,7 +609,11 @@ var mt2=activeTracksRef.current||tracks;var qTrk=null;for(var qi=0;qi<mt2.length
|
||||
const cachedSf=(instrumentSelectorData?.soundfonts||[]).find(s=>s.id===instrumentId||s.id===sfIdParam);if(cachedSf&&cachedSf.presets){setSfPresets(cachedSf.presets);}else{window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(e=>{console.error('listSoundfontInstruments failed:',e);setSfPresets([]);});}}else{setTrackInstrumentWithUndo(trackId,instrumentId,displayName);}};const[activeTool,setActiveTool]=useState('select');// 'select' | 'grab' | 'razor'
|
||||
const[snapValue,onSnapChangeue]=useState('1');// 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
|
||||
const snapTime=(time,snapValue,bpmVal)=>{if(snapValue==='free')return time;const beatDuration=60/parseFloat(bpmVal||120);let divisor=1;if(snapValue==='4')divisor=4;else if(snapValue==='1')divisor=1;else if(snapValue==='1/2')divisor=0.5;else if(snapValue==='1/4')divisor=0.25;else if(snapValue==='1/8')divisor=0.125;else if(snapValue==='1/16')divisor=0.0625;else if(snapValue==='1/32')divisor=0.03125;const gridSpacing=beatDuration*divisor;return Math.round(time/gridSpacing)*gridSpacing;};const snapValueRef=useRef(snapValue);snapValueRef.current=snapValue;const bpmRef=useRef(bpm);bpmRef.current=bpm;// BMP for Tempo Track - LOOP_EDITOR_2.md §6
|
||||
const[selectedTrackId,setSelectedTrackId]=useState('1');const[selectedItemIds,setSelectedItemIds]=useState(new Set());const selectedItemIdsRef=useRef(new Set());selectedItemIdsRef.current=selectedItemIds;const[currentTime,setCurrentTime]=useState(0);const[isPlaying,setIsPlaying]=useState(false);const[selectionStart,setSelectionStart]=useState(null);const[selectionEnd,setSelectionEnd]=useState(null);const[selectionFollowsTempo,setSelectionFollowsTempo]=useState(true);const selectionRef=useRef({start:null,end:null});selectionRef.current={start:selectionStart,end:selectionEnd};const[selectionMode,setSelectionMode]=useState(null);// 'global' (from ruler) | 'local' (from track)
|
||||
const[selectedTrackId,setSelectedTrackId]=useState('1');const[selectedItemIds,setSelectedItemIds]=useState(new Set());const selectedItemIdsRef=useRef(new Set());selectedItemIdsRef.current=selectedItemIds;const[currentTime,setCurrentTime]=useState(0);const[isPlaying,setIsPlaying]=useState(false);// Ref theo dõi isPlaying — setTimeout VU trigger (main-midi delayed + section
|
||||
// delayed) check ref này trước khi fire: stop → isPlaying=false → setTimeout
|
||||
// sót không fire → VU không nhảy sau khi âm đã dừng (user: VU vẫn animation
|
||||
// khi hết âm).
|
||||
const isPlayingRef=useRef(false);isPlayingRef.current=isPlaying;const[selectionStart,setSelectionStart]=useState(null);const[selectionEnd,setSelectionEnd]=useState(null);const[selectionFollowsTempo,setSelectionFollowsTempo]=useState(true);const selectionRef=useRef({start:null,end:null});selectionRef.current={start:selectionStart,end:selectionEnd};const[selectionMode,setSelectionMode]=useState(null);// 'global' (from ruler) | 'local' (from track)
|
||||
const[sweepSelect,setSweepSelect]=useState(null);const isSweepingRef=useRef(false);const sweepStartRef=useRef(0);const sweepTrackIdRef=useRef(null);const sweepStartYRef=useRef(0);const sweepEndYRef=useRef(0);const sweepSelectRef=useRef(null);const pendingDragRef=useRef(null);// { trackId, itemType, itemId, clickOffset, startX, startY }
|
||||
const handleSectionItemDragStartRef=useRef(null);const[localSelectionTrackId,setLocalSelectionTrackId]=useState(null);const[localSelectionStart,setLocalSelectionStart]=useState(null);const[localSelectionEnd,setLocalSelectionEnd]=useState(null);const[zoom,setZoom]=useState(()=>{// Persist zoom qua reload (user yêu cầu giữ kích thước items sau refresh)
|
||||
try{const v=parseFloat(localStorage.getItem('sf_zoom'));if(isFinite(v)&&v>0)return v;}catch(e){}return 100;});React.useEffect(()=>{try{localStorage.setItem('sf_zoom',String(zoom));}catch(e){}// Zoom TIMELINE (App) — serialize project dùng cái này (window.__currentZoom
|
||||
@@ -734,7 +738,9 @@ mainResumeRef.current=null;}}},[activeTab]);const[selectionCleared,setSelectionC
|
||||
// Detect MAIN/SECTION play vừa DỪNG (hết notes/projectEnd/stop): nếu đang ở
|
||||
// PIANO ROLL tab (không play tab) → playhead về 0 — user: main play hết →
|
||||
// không còn midi note → piano roll phải về đầu (không giữ local cuối).
|
||||
const prevIsPlayingRef=useRef(false);useEffect(()=>{const wasPlaying=prevIsPlayingRef.current;prevIsPlayingRef.current=isPlaying;if(wasPlaying&&!isPlaying){const _tabId=activeTabRef.current;if(_tabId&&_tabId.startsWith('midi_')){const _prSt=subTabsRef.current.find(s=>s.id===_tabId);if(_prSt&&!_prSt.isPlaying){setSubTabs(prev=>prev.map(s=>s.id===_tabId?{...s,currentTime:0}:s));}}}},[isPlaying]);const midiVuActivityRef=useRef({});const triggerMidiVuActivity=(trackId,velocity)=>{if(!trackId)return;const velFactor=typeof velocity==='number'?velocity>1?velocity/127:velocity:0.8;const peak=Math.min(1.0,Math.max(0.15,velFactor));midiVuActivityRef.current[trackId]=peak;};window.triggerMidiVuActivity=triggerMidiVuActivity;// ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ──
|
||||
const prevIsPlayingRef=useRef(false);useEffect(()=>{const wasPlaying=prevIsPlayingRef.current;prevIsPlayingRef.current=isPlaying;if(wasPlaying&&!isPlaying){const _tabId=activeTabRef.current;if(_tabId&&_tabId.startsWith('midi_')){const _prSt=subTabsRef.current.find(s=>s.id===_tabId);if(_prSt&&!_prSt.isPlaying){setSubTabs(prev=>prev.map(s=>s.id===_tabId?{...s,currentTime:0}:s));}}}},[isPlaying]);const midiVuActivityRef=useRef({});const triggerMidiVuActivity=(trackId,velocity)=>{if(!trackId)return;const velFactor=typeof velocity==='number'?velocity>1?velocity/127:velocity:0.8;const peak=Math.min(1.0,Math.max(0.15,velFactor));midiVuActivityRef.current[trackId]=peak;// Log nguồn trigger (rate-limited 1/20 fire): xác định ai còn fire VU khi
|
||||
// âm đã ngừng (user: VU vẫn animation sau khi hết âm).
|
||||
try{const _tf=window.__trigFrame=(window.__trigFrame||0)+1;if(_tf%20===1){console.log('[VUTrig]',trackId,'vel=',velocity,'peak=',peak.toFixed(2),'play=',isPlaying,'tab=',activeTabRef.current);}}catch(e){}};window.triggerMidiVuActivity=triggerMidiVuActivity;// ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ──
|
||||
const[tempTabActive,setTempTabActive]=useState(false);const[tempTabBuffer,setTempTabBuffer]=useState(null);const[tempTabTrackId,setTempTabTrackId]=useState(null);const[tempTabOrigStart,setTempTabOrigStart]=useState(0);const[tempTabOrigEnd,setTempTabOrigEnd]=useState(0);const tempTabCanvasRef=useRef(null);// Effect parameters for temp tab
|
||||
const[tempTabEffects,setTempTabEffects]=useState({reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0});// ── Auth / User State ──
|
||||
const[currentUser,setCurrentUser]=useState(null);const[authModalOpen,setAuthModalOpen]=useState(false);const[authMode,setAuthMode]=useState('login');// 'login' | 'register' | 'force_change'
|
||||
@@ -1092,7 +1098,9 @@ ensureMidiCapture(track,activeTrackNodesRef.current[track.id]);var trkCh=assignT
|
||||
if(track.synth_engine&&track.synth_engine.type==='soundfont'&&track.synth_engine.soundfont_id){window.SonicSF.selectInstrument(trkCh,track.synth_engine.soundfont_bank||0,track.synth_engine.soundfont_program||0,track.synth_engine.soundfont_id);}const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const itemEndSec=item.startTime+(item.duration||4);const notes=item.notes||[];notes.forEach(note=>{// note start/duration is in beats (for MIDI items)
|
||||
const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;if(noteStartSec>=itemEndSec)return;// skip notes outside item's visual duration
|
||||
const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime<noteEndSec){const durationMs=noteDurSec*1000;const program=track._isSectionClone?track.instrumentProgram:track.instrumentProgram!==undefined?track.instrumentProgram:0;if(offsetTime<noteStartSec){const delay=noteStartSec-offsetTime;const startTime=context.currentTime+delay;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durationMs,startTime,program,gainNode,trkCh,track.synth_engine);// Trigger VU meter flash when the note starts playing
|
||||
setTimeout(()=>{if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(track.id,note.velocity||0.8);}},delay*1000);}else{const playOffset=offsetTime-noteStartSec;const remainingDurMs=(noteEndSec-offsetTime)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,gainNode,trkCh,track.synth_engine);// Trigger VU meter flash instantly
|
||||
setTimeout(()=>{// ⚠️ Guard: stop → setTimeout sót không được fire (VU nhảy
|
||||
// sau khi âm đã dừng — user bug 05:45).
|
||||
if(!isPlayingRef.current)return;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(track.id,note.velocity||0.8);}},delay*1000);}else{const playOffset=offsetTime-noteStartSec;const remainingDurMs=(noteEndSec-offsetTime)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,gainNode,trkCh,track.synth_engine);// Trigger VU meter flash instantly
|
||||
if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(track.id,note.velocity||0.8);}}}});});}// If track has instrumentId set but no MIDI items, create scheduled oscillators
|
||||
if(track.instrumentId&&midiItems.length===0&&track.buffer){// Play audio normally (the buffer has the audio data)
|
||||
// This block is intentionally empty - audio clips already play above
|
||||
@@ -1104,11 +1112,14 @@ const subNodeObj=activeTrackNodesRef.current[track.id+'_sub_'+subTrack.id];if(su
|
||||
const subClips=subTrack.clips||[];subClips.forEach(clip=>{if(!clip.buffer)return;const clipStartLocal=clip.startTime||0;const clipDurLocal=clip.buffer.duration/(clip.speed||1.0);const clipStartMain=secStart+clipStartLocal;const clipEndMain=clipStartMain+clipDurLocal;// Only play within the bounds of [secStart, secEnd]
|
||||
const activeStart=Math.max(clipStartMain,secStart);const activeEnd=Math.min(clipEndMain,secEnd);if(activeStart<activeEnd&&offsetTime<activeEnd){const source=context.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(subNode);if(offsetTime<activeStart){const delay=activeStart-offsetTime;const playOffset=activeStart-clipStartMain;const playDuration=activeEnd-activeStart;source.start(context.currentTime+delay,playOffset*(clip.speed||1.0),playDuration);activeSourcesRef.current.push(source);}else{const playOffset=offsetTime-clipStartMain;const playDuration=activeEnd-offsetTime;source.start(context.currentTime,playOffset*(clip.speed||1.0),playDuration);activeSourcesRef.current.push(source);}}});// 2. Play MIDI items in subTrack
|
||||
const subMidiItems=subTrack.midiItems||[];if(window.SonicSF&&subMidiItems.length>0){subMidiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSecLocal=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteStartMain=secStart+noteStartSecLocal;const noteEndMain=noteStartMain+noteDurSec;// Only play if the note starts inside the Section item bounds and hasn't finished yet
|
||||
if(noteStartMain>=secStart&¬eStartMain<secEnd&&offsetTime<noteEndMain){const notePlayEndMain=Math.min(noteEndMain,secEnd);const program=subTrack.instrumentProgram;var subCh=assignTrackMidiChannel(subTrack,secTracks);if(offsetTime<noteStartMain){const delay=noteStartMain-offsetTime;const startTime=context.currentTime+delay;const playDurMs=(notePlayEndMain-noteStartMain)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,playDurMs,startTime,program,subNode,subCh,subTrack.synth_engine);}else{const remainingDurMs=(notePlayEndMain-offsetTime)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,subNode,subCh,subTrack.synth_engine);}// Trigger VU meter flash đúng nhịp (giống main-track midi 19066)
|
||||
// Cả subTrack (VU section tab) + parent track (VU MAIN — section
|
||||
// item đang play) — midiVuActivity keyed theo track id → trigger
|
||||
// cả 2 để VU main nhảy theo section.
|
||||
if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity(track.id,note.velocity||0.8);}}});});}});});}});updateSfRouting();};// Solo playback for Local Selection Loop (LOOP_MAKER.md §2.2)
|
||||
if(noteStartMain>=secStart&¬eStartMain<secEnd&&offsetTime<noteEndMain){const notePlayEndMain=Math.min(noteEndMain,secEnd);const program=subTrack.instrumentProgram;var subCh=assignTrackMidiChannel(subTrack,secTracks);if(offsetTime<noteStartMain){const delay=noteStartMain-offsetTime;const startTime=context.currentTime+delay;const playDurMs=(notePlayEndMain-noteStartMain)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,playDurMs,startTime,program,subNode,subCh,subTrack.synth_engine);// Trigger VU meter flash đúng nhịp (cả subTrack — VU
|
||||
// section tab + parent track — VU MAIN): MIDI section đi
|
||||
// SF → masterBus.input (không qua sub-node analyser) →
|
||||
// VU track chỉ nhảy qua midiVuActivity. (Bị mất khi
|
||||
// commit gộp — khôi phục fix 00:30/02:00.)
|
||||
setTimeout(()=>{// ⚠️ Guard: stop → setTimeout sót không được fire (VU
|
||||
// nhảy sau khi âm đã dừng — user bug 05:45).
|
||||
if(!isPlayingRef.current)return;if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity(track.id,note.velocity||0.8);}},delay*1000);}else{const remainingDurMs=(notePlayEndMain-offsetTime)*1000;window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,subNode,subCh,subTrack.synth_engine);if(window.triggerMidiVuActivity){window.triggerMidiVuActivity(subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity(track.id,note.velocity||0.8);}}}});});}});});}});updateSfRouting();};// Solo playback for Local Selection Loop (LOOP_MAKER.md §2.2)
|
||||
const startLocalTrackPlayback=(trackId,offsetTime)=>{const context=getAudioContext();const track=tracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',buffer:track.buffer,startTime:track.startTime||0,name:track.name,speed:track.speed||1.0}]:[];// Get or create persistent gain & panner for real-time control
|
||||
const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackNodesRef.current[track.id].pannerNode;clips.forEach(clip=>{if(!clip.buffer)return;const source=context.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(gainNode);// NOTE: do NOT connect gainNode → pannerNode directly here — the track
|
||||
// node already wires gainNode → fxEntry → [FX modules] → fxLegacyIn →
|
||||
@@ -1347,17 +1358,34 @@ const prefsRef=useRef({});prefsRef.current={showAIPanel,showExportPanel,showSele
|
||||
const masterVUAnimRef=useRef(null);useEffect(()=>{function tick(){try{// Khi STOP (không play, không recording): vẽ VU RỖNG — không đọc analyser/
|
||||
// activity → hết animation dính + master không full sau khi dừng play.
|
||||
const isRecActive=activeAudioRecordersRef.current&&Object.keys(activeAudioRecordersRef.current).length>0;if(!isPlaying&&!isRecActive){setMasterVU(0);setMasterMeterPeak(0);Object.keys(trackVuRefs.current).forEach(k=>{const c=trackVuRefs.current[k];if(!c)return;if(k.endsWith('_mixer'))drawMixerVuMeter(c,0);else drawVuMeter(c,-60);});masterVUAnimRef.current=requestAnimationFrame(tick);return;}// 1. Master VU Meter
|
||||
if(masterBus&&masterBus.analyser){const data=new Uint8Array(128);masterBus.analyser.getByteTimeDomainData(data);let peak=0;for(let i=0;i<data.length;i++){const v=Math.abs(data[i]-128)/128;if(v>peak)peak=v;}setMasterVU(peak);setMasterMeterPeak(prev=>Math.max(prev*0.97,peak));}// 2. Track VU Meters
|
||||
if(masterBus&&masterBus.analyser){const data=new Uint8Array(128);masterBus.analyser.getByteTimeDomainData(data);let peak=0;for(let i=0;i<data.length;i++){const v=Math.abs(data[i]-128)/128;if(v>peak)peak=v;}// Deadband: bỏ noise nền (log 06:00 — master peak 0.008 liên tục dù
|
||||
// không âm → master VU nhảy nhẹ mãi)
|
||||
if(peak<0.01)peak=0;setMasterVU(peak);setMasterMeterPeak(prev=>Math.max(prev*0.97,peak));// Log master VU khi đang nhảy (chẩn đoán 06:00 — VU nhảy ngoài vùng âm)
|
||||
if(peak>0.001){try{console.log('[VU-master] peak=',peak.toFixed(3));}catch(e){}}}// 2. Track VU Meters
|
||||
const trackNodes=activeTrackNodesRef.current||{};const activeKeys=Object.keys(trackVuRefs.current);activeKeys.forEach(key=>{const trackId=key.replace('_mixer','');const isRecordingThisTrack=activeAudioRecordersRef.current&&activeAudioRecordersRef.current[trackId];if(isRecordingThisTrack)return;const node=trackNodes[trackId];const canvas=trackVuRefs.current[key];if(!canvas)return;// Section tab: track nodes nằm dưới key <parent>_sub_<trackId> —
|
||||
// lookup plain key trả undefined → VU không animation. Fallback scan _sub_.
|
||||
const vNode=node||function(){for(var k in trackNodes){if(k.endsWith('_sub_'+trackId))return trackNodes[k];}return null;}();// Only animate when real audio actually passes through the track (solo/mute aware)
|
||||
var vuTracks=activeTracksRef.current||[];var anySoloVU=vuTracks.some(function(t){return t.solo;});var vuTrack=vuTracks.find(function(t){return t.id===trackId;});var isAudible=vuTrack?anySoloVU?!!vuTrack.solo:!vuTrack.muted:true;let audioPeak=0;if(isAudible&&vNode&&vNode.analyserNode){const analyser=vNode.analyserNode;const data=new Uint8Array(128);analyser.getByteTimeDomainData(data);for(let i=0;i<data.length;i++){const v=Math.abs(data[i]-128)/128;if(v>audioPeak)audioPeak=v;}}// Section item trên MAIN track: VU theo sub-nodes của track này (key
|
||||
// <trackId>_sub_*) — âm section đi thẳng masterBus.input (không qua
|
||||
// node chính) → node chính không có tín hiệu → VU track đứng yên.
|
||||
if(audioPeak<=0.001){for(var sk in trackNodes){if(sk.indexOf(trackId+'_sub_')===0){const sn=trackNodes[sk];if(sn&&sn.analyserNode){const d2=new Uint8Array(128);sn.analyserNode.getByteTimeDomainData(d2);for(let i=0;i<d2.length;i++){const v=Math.abs(d2[i]-128)/128;if(v>audioPeak)audioPeak=v;}}}}}// midiVuActivityRef được set bởi triggerMidiVuActivity — cả khi play
|
||||
if(audioPeak<=0.001){for(var sk in trackNodes){if(sk.indexOf(trackId+'_sub_')===0){const sn=trackNodes[sk];if(sn&&sn.analyserNode){const d2=new Uint8Array(128);sn.analyserNode.getByteTimeDomainData(d2);for(let i=0;i<d2.length;i++){const v=Math.abs(d2[i]-128)/128;if(v>audioPeak)audioPeak=v;}}}}}// MIDI THỰC: đọc từ SF output (âm post-FluidSynth) — thay velocity
|
||||
// trigger (GIẢ — fire lúc schedule — click playhead → re-schedule →
|
||||
// VU nhảy dù âm không ra — log 06:00: midi=0.98 nhưng master=0.008).
|
||||
// Track MIDI đi SF → masterBus (không qua node analyser) → tap tại SF
|
||||
// output gain.
|
||||
let sfAudioPeak=0;let _sfTapped=false;if(audioPeak<=0.001){try{if(window.SonicSF&&window.SonicSF.getOutputNode){const sfNode=window.SonicSF.getOutputNode();if(sfNode){if(!sfNode.__vuAnalyser){const _sa=getAudioContext().createAnalyser();_sa.fftSize=256;_sa.smoothingTimeConstant=0.2;sfNode.connect(_sa);sfNode.__vuAnalyser=_sa;}_sfTapped=true;const d3=new Uint8Array(128);sfNode.__vuAnalyser.getByteTimeDomainData(d3);for(let i=0;i<d3.length;i++){const v=Math.abs(d3[i]-128)/128;if(v>sfAudioPeak)sfAudioPeak=v;}}}}catch(e){}}// midiVuActivityRef được set bởi triggerMidiVuActivity — cả khi play
|
||||
// MIDI item LẪN khi ARM + nhấn phím MIDI keyboard (preview) — nên VU
|
||||
// nhảy trong cả 2 trường hợp (không chặn bởi isPlaying).
|
||||
let midiPeak=isAudible?midiVuActivityRef.current[trackId]||0:0;if(midiPeak>0){midiVuActivityRef.current[trackId]=midiPeak*0.90;if(midiVuActivityRef.current[trackId]<0.01){midiVuActivityRef.current[trackId]=0;}}const peak=Math.max(audioPeak,midiPeak);const db=peak>0?20*Math.log10(peak):-60;if(peak>0.001){if(key.endsWith('_mixer')){drawMixerVuMeter(canvas,peak);}else{drawVuMeter(canvas,db);}}else{if(key.endsWith('_mixer')){drawMixerVuMeter(canvas,0);}else{drawVuMeter(canvas,-60);}}});}catch(e){console.warn('VU tick error:',e);}masterVUAnimRef.current=requestAnimationFrame(tick);}masterVUAnimRef.current=requestAnimationFrame(tick);return()=>{if(masterVUAnimRef.current)cancelAnimationFrame(masterVUAnimRef.current);};},[isPlaying]);const handleOpenProject=async(projectId,projectName)=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;// Bỏ qua zoom ≤ 1 (data cũ lưu 1.0 — lỗi __currentZoom của Media
|
||||
let midiPeak=isAudible?midiVuActivityRef.current[trackId]||0:0;// SF analyser đã tap → CHỈ dùng âm THỰC (velocity trigger = giả — bỏ)
|
||||
if(_sfTapped)midiPeak=0;if(midiPeak>0){// Decay nhanh hơn (0.72/frame ≈ tắt sau ~0.35s) — hết note → VU
|
||||
// dừng nhanh, không "vẫn diễn animation" khi không còn âm.
|
||||
midiVuActivityRef.current[trackId]=midiPeak*0.72;if(midiVuActivityRef.current[trackId]<0.01){midiVuActivityRef.current[trackId]=0;}}// Gộp âm MIDI THỰC vào audioPeak
|
||||
if(sfAudioPeak>audioPeak)audioPeak=sfAudioPeak;// Deadband: bỏ noise nền analyser (log 06:05 — audio=0.008 dù không
|
||||
// âm → VU hiển thị đoạn nhỏ "vẽ nhưng không animation") — hết âm →
|
||||
// VU tắt NGAY LẬP TỨC (user yêu cầu).
|
||||
if(audioPeak<0.01)audioPeak=0;const peak=Math.max(audioPeak,midiPeak);const db=peak>0?20*Math.log10(peak):-60;// Log chẩn đoán VU: CHỈ in khi peak > 0 (VU đang nhảy thật) — xác định
|
||||
// nguồn nào còn nhảy khi playhead ngoài vùng âm (user bug 06:00).
|
||||
if(peak>0.001){try{console.log('[VU]',trackId,'audio=',audioPeak.toFixed(3),'midi=',midiPeak.toFixed(3),'key=',key);}catch(e){}}if(peak>0.001){if(key.endsWith('_mixer')){drawMixerVuMeter(canvas,peak);}else{drawVuMeter(canvas,db);}}else{if(key.endsWith('_mixer')){drawMixerVuMeter(canvas,0);}else{drawVuMeter(canvas,-60);}}});}catch(e){console.warn('VU tick error:',e);}masterVUAnimRef.current=requestAnimationFrame(tick);}masterVUAnimRef.current=requestAnimationFrame(tick);return()=>{if(masterVUAnimRef.current)cancelAnimationFrame(masterVUAnimRef.current);};},[isPlaying]);const handleOpenProject=async(projectId,projectName)=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;// Bỏ qua zoom ≤ 1 (data cũ lưu 1.0 — lỗi __currentZoom của Media
|
||||
// Explorer) — áp dụng mới giữ zoom hiện tại/localStorage.
|
||||
if(result.zoom>1&&setZoom)setZoom(result.zoom);if(result.masteringSettings)setMasteringSettings(result.masteringSettings);}else{restoredTracks=(parsed.tracks||[]).map(function(t){const rest=Object.assign({},t);delete rest.height;return Object.assign({},rest,{buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null});});}setTracks(restoredTracks);loadAudioBuffersForTracks(restoredTracks).catch(function(err){console.warn('loadAudioBuffersForTracks error:',err);});trackMidiChannelsRef.current={};preloadTrackInstruments(restoredTracks).catch(function(err){console.warn('preloadTrackInstruments error:',err);});setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(proj.name);setCurrentProjectId(proj.id);if(restoredSessionTabs.length>0)setSessionTabs(restoredSessionTabs);if(restoredSubTabs.length>0)setSubTabs(restoredSubTabs);localStorage.setItem('sonic_project_name',proj.name);localStorage.setItem('sonic_project_id',proj.id);showToast('Đã nạp dự án "'+proj.name+'" thành công!',"success");}catch(e){showToast(e.message||"Lỗi khi nạp dự án","error");}}});};return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"h-full w-full flex flex-col bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("header",{className:"h-9 bg-[#2e2e2e] border-b border-[#181818] flex items-center px-1 shrink-0 select-none"},[{label:'File',items:[{label:'New Project',icon:'file-plus',shortcut:'Ctrl+N',action:()=>{// New project: đóng hết tab + reset MỌI trạng thái về mặc định
|
||||
try{stopAllPlayback();}catch(e){}setSubTabs([]);setSessionTabs([]);setActiveTab('main');// Reset instrument/channel/route context đã load
|
||||
|
||||
@@ -24,7 +24,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=202608070535" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608070610" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -2533,3 +2533,53 @@
|
||||
(2) Input End Bar (dock 26436 + statusbar 27846): `setSelectionEnd(t + spb)` → `setSelectionEnd(t)` (selection tới vạch bar b — khớp hiển thị); `setNumberBar(b − beginBar)`.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070535), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → quét 8 bars từ 0 → BARS hiển thị "0 - 8 # 8"; nhập End Bar = 8 → selection tới vạch 8 (8 bars).
|
||||
|
||||
### [2026-08-07 05:40] Task: MAIN SESSION — play section item có âm nhưng track VU không hiển thị
|
||||
- **Báo cáo user:** play section item ở MAIN — âm phát ra (track) nhưng VU meter không nhảy.
|
||||
- **Phân tích:** MIDI section (FluidSynth SF) đi thẳng masterBus.input (midiAudible main rỗng → setOutputDestination(null)) — KHÔNG qua node track/sub-node analyser → audioPeak = 0 — VU track chỉ nhảy qua `triggerMidiVuActivity` (midiVuActivityRef). Trigger cũ (19511-19518) VẪN CÒN (fire lúc schedule — không setTimeout cho note delayed). Chưa rõ vì sao VU đứng yên — cần log.
|
||||
- **FIX (app.jsx):** (1) bổ sung trigger VU cho nhánh note DELAYED (setTimeout khớp nhịp — subTrack.id + track.id) — nhánh instant đã có; (2) thêm log chẩn đoán VU tick: `[VU] <trackId> audio=... midi=... vNode=... analyser=... canvas=...` (rate-limited ~1.3 lần/giây).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070540), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → play MAIN (section item MIDI) → dán log `[VU]` — xác định: midi= có >0 không (trigger fire?); vNode/analyser/canvas có tồn tại không.
|
||||
|
||||
### [2026-08-07 05:45] Task: Track VU vẫn diễn animation khi không còn âm (MAIN/SECTION-TAB)
|
||||
- **Báo cáo user:** track VU meter không diễn đúng — hết âm vẫn nhảy animation.
|
||||
- **Phân tích:** VU track (MIDI section) chỉ nhảy qua `midiVuActivityRef` (trigger fire lúc schedule). Trigger cũ fire NGAY lúc schedule (kể cả note delay xa — VU nhảy trước âm / note bị cắt vẫn flash) + TRÙNG với trigger mới (05:40) → VU nhảy sai nhịp/kéo dài. Decay 0.9/frame (~0.75s) — sau note cuối VU còn diễn ~0.75s.
|
||||
- **FIX (app.jsx):**
|
||||
(1) XÓA trigger cũ fire-ngay (sau block — chạy cho mọi note cả delayed) — chỉ giữ trigger mới: nhánh delayed (setTimeout delay*1000 — khớp nhịp) + nhánh instant.
|
||||
(2) Decay nhanh: `midiPeak * 0.90` → `0.72` (tắt sau ~0.35s — hết note → VU dừng nhanh).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070545), `wiki.md`. Rebuild precompiled (build PASS — triggerMidiVuActivity còn 8 chỗ).
|
||||
- **Ghi chú/Test:** hard refresh → play MAIN (section MIDI) → hết notes → VU dừng trong ~0.35s; nếu VU VẪN nhảy khi hết âm → dán log `[VU]` (midi= giá trị bao nhiêu khi không còn âm).
|
||||
|
||||
### [2026-08-07 05:50] Task: VU vẫn animation khi hết âm — log nguồn trigger [VUTrig]
|
||||
- **Log user (0545):** âm ngừng nhưng `[VU] 1 audio=0.000 midi=0.058` → `midi=0.661` — midiPeak TĂNG LẠI → triggerMidiVuActivity vẫn fire định kỳ sau khi hết âm.
|
||||
- **FIX (app.jsx):** thêm log `[VUTrig] <trackId> vel=... peak=... play=<isPlaying> tab=<activeTab>` vào `triggerMidiVuActivity` (rate-limited 1/20 fire) — xác định AI còn fire (setTimeout sót từ main-midi delayed? re-schedule? keyboard preview?).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070550), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → play MAIN (hết âm — VU vẫn nhảy) → dán log `[VUTrig]` — cho biết track nào + lúc nào (play=?) vẫn fire.
|
||||
|
||||
### [2026-08-07 05:55] Task: CHỐT — VU vẫn nhảy khi hết âm = setTimeout VU trigger sót sau stop
|
||||
- **Log user (0550):** sau STOP (`[Play] click isPlaying=true` → stop) — `[VUTrig] 1 vel=0.83/0.78/0.95 ... play= false` — **trigger VU vẫn fire sau khi play đã dừng**.
|
||||
- **Nguyên nhân:** setTimeout VU trigger của note DELAYED (main-midi 19351 + section delayed — khôi phục 05:40) KHÔNG bị hủy khi stop → fire MUỘN sau stop → midiVuActivity set lại → VU nhảy (decay 0.72 vẫn chưa kịp về 0).
|
||||
- **FIX (app.jsx):** thêm `isPlayingRef` (App — sync mỗi render) + guard `if (!isPlayingRef.current) return;` ở ĐẦU 2 setTimeout VU trigger (main-midi + section delayed) — stop → isPlaying=false → setTimeout sót không fire → VU dừng ngay khi hết âm/stop.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070555), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → play MAIN (MIDI) → STOP giữa chừng → log `[VUTrig]` KHÔNG còn fire sau stop (chỉ fire lúc play= true); hết bài tự nhiên → VU dừng trong ~0.35s.
|
||||
|
||||
### [2026-08-07 06:00] Task: VU vẫn nhảy khi playhead ngoài vùng âm — log nguồn peak>0
|
||||
- **Báo cáo user:** VU vẫn diễn animation khi playhead di chuyển ra khỏi vùng có âm. Log 0555: track VU `midi=0.000` liên tục (đã dừng đúng) — NHƯNG user vẫn thấy nhảy → nguồn KHÁC (master VU? key khác? peak giữa 2 log rate-limited?).
|
||||
- **FIX (app.jsx):** đổi log chẩn đoán — (1) `[VU] <trackId> audio=... midi=... key=<key>` CHỈ in khi peak > 0.001 (VU nhảy thật — không rate-limit); (2) thêm `[VU-master] peak=...` (master VU khi nhảy) — xác định VU nào còn nhảy ngoài vùng âm.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070600), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → play MAIN → playhead ra khỏi vùng âm → nếu VU vẫn nhảy → dán log `[VU]`/`[VU-master]` xuất hiện lúc đó (cho biết track/key/nguồn audio hay midi).
|
||||
|
||||
### [2026-08-07 06:05] Task: SECTION-TAB — VU nhảy theo velocity trigger (giả) — đọc âm THỰC từ SF output
|
||||
- **Log user (0600):** SECTION-TAB — click playhead tới vị trí không âm → VU vẫn nhảy: `[VU] 1 midi= 0.98 → 0.85 → ...` (tăng lại theo chu kỳ — trigger fire lúc schedule/re-schedule) nhưng `[VU-master] peak= 0.008` (âm thực gần im) — **VU GIẢ (velocity trigger), không theo âm thực**. Master VU nhảy nhẹ 0.008 liên tục (noise nền).
|
||||
- **FIX (app.jsx VU tick):**
|
||||
(1) Tap analyser trên SF output (`SonicSF.getOutputNode()` — âm MIDI THỰC post-FluidSynth) — `sfAudioPeak` gộp vào audioPeak — VU MIDI theo âm THỰC.
|
||||
(2) Khi SF analyser đã tap → `midiPeak = 0` (bỏ velocity trigger giả) — hết note → SF im → VU 0 ngay (không nhảy giả sau click playhead/re-schedule).
|
||||
(3) Master VU deadband: `peak < 0.01 → 0` — hết noise nền 0.008.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070605), `wiki.md`. Rebuild precompiled (build PASS).
|
||||
- **Ghi chú/Test:** hard refresh → SECTION-TAB play → click playhead tới vị trí không âm → VU KHÔNG nhảy (SF im → 0); vị trí có âm → VU nhảy theo âm thực; master VU không nhảy noise.
|
||||
|
||||
### [2026-08-07 06:10] Task: VU vẽ đoạn ngắn (0.008) khi hết âm — deadband track analyser
|
||||
- **Log user (0605):** `[VU] 1 audio= 0.008 midi= 0.000` — VU vẽ đoạn nhỏ (~-42dB) dù không âm — noise nền trên track analyser (như master 0.008 — đã deadband 06:05).
|
||||
- **FIX (app.jsx):** track audioPeak deadband — `if (audioPeak < 0.01) audioPeak = 0;` (sau khi gộp sfAudioPeak) — hết âm → VU tắt NGAY (peak 0 → db -60 → vẽ 0).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070610), `wiki.md`. Rebuild precompiled (build PASS — Deadband ×2: master + track).
|
||||
- **Ghi chú/Test:** hard refresh → play → playhead ra khỏi vùng âm → VU TẮT NGAY (không còn đoạn 0.008); log `[VU]` không còn audio=0.008.
|
||||
|
||||
Reference in New Issue
Block a user