fix: patch precompiled JS - thêm stopNote + duration 60000
app.precompiled.js được build từ app.jsx cũ (10:54), thiếu: - SonicSF.stopNote() trong MIDI note-off handler → note không tắt - duration 500ms (quá ngắn, nên 60000ms = hold) Patch thủ công bằng Python replace trên minified code.
This commit is contained in:
@@ -185,7 +185,7 @@ const cachedSf=(instrumentSelectorData?.soundfonts||[]).find(s=>s.id===instrumen
|
||||
const[snapValue,setSnapValue]=useState('free');// 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
|
||||
const snapTime=(time,snapVal,bpmVal)=>{if(snapVal==='free')return time;const beatDuration=60/parseFloat(bpmVal||120);let divisor=1;if(snapVal==='4')divisor=4;else if(snapVal==='1')divisor=1;else if(snapVal==='1/2')divisor=0.5;else if(snapVal==='1/4')divisor=0.25;else if(snapVal==='1/8')divisor=0.125;else if(snapVal==='1/16')divisor=0.0625;else if(snapVal==='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[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[localSelectionTrackId,setLocalSelectionTrackId]=useState(null);const[localSelectionStart,setLocalSelectionStart]=useState(null);const[localSelectionEnd,setLocalSelectionEnd]=useState(null);const[zoom,setZoom]=useState(100);const[isLoopingSelection,setIsLoopingSelection]=useState(false);const[beginBar,setBeginBar]=useState(0);const[endBar,setEndBar]=useState(0);const[numberBar,setNumberBar]=useState(1);const[subTabHeight,setSubTabHeight]=useState(96);const[isExporting,setIsExporting]=useState(false);const[projectName,setProjectName]=useState(()=>localStorage.getItem('sonic_project_name')||'');const[currentProjectId,setCurrentProjectId]=useState(()=>localStorage.getItem('sonic_project_id')||null);const[saveProjectModalOpen,setSaveProjectModalOpen]=useState(false);const[saveAsModalOpen,setSaveAsModalOpen]=useState(false);const soloedTrack=tracks.find(t=>t.solo);const soloedTrackId=soloedTrack?soloedTrack.id:null;const[toastMessage,setToastMessage]=useState(null);const[audioDevices,setAudioDevices]=useState([]);const[midiDevices,setMidiDevices]=useState([]);const[selectedMidiInputId,setSelectedMidiInputId]=useState('');const handleMidiInputSelect=id=>{setSelectedMidiInputId(id);if(window.SonicRecorderManager){window.SonicRecorderManager.setSelectedMidiInputId(id);}};useEffect(()=>{if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices){navigator.mediaDevices.enumerateDevices().then(devices=>{setAudioDevices(devices.filter(d=>d.kind==='audioinput'));}).catch(err=>console.log('Enumerate audio devices error:',err));}if(navigator.requestMIDIAccess){navigator.requestMIDIAccess().then(access=>{const inputs=[];for(let input of access.inputs.values()){inputs.push(input);input.onmidimessage=msg=>{console.log(`[DevLog] [Raw MIDI] Received from "${input.name}" (ID: ${input.id}) - Data:`,Array.from(msg.data));if(msg.data.length<3)return;const cmd=msg.data[0]>>4;const pitch=msg.data[1];const velocity=msg.data[2];if(cmd===0x9&&velocity>0){lastMidiNoteRef.current={pitch,velocity,startTime:performance.now(),length:0};setLastMidiNote({pitch,velocity,length:0,time:Date.now()});activeMidiPitchesRef.current.add(pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));{if(window.SonicSF){const arSub=subTabsRef&&subTabsRef.current&&activeTabRef&&subTabsRef.current.find(s=>s.id===activeTabRef.current&&s.type==='PIANO_ROLL'&&s.isArmed);if(arSub){window.SonicSF.playNote(pitch,velocity,500,undefined,arSub.instrumentProgram,null,undefined,arSub.synth_engine);}else{const armedTrack=activeTracksRef.current?activeTracksRef.current.find(t=>t.isArmed):null;if(armedTrack){const prog=armedTrack.instrumentProgram;const se=armedTrack.synth_engine;const dest=activeTrackNodesRef.current[armedTrack.id]?.gainNode||null;window.SonicSF.playNote(pitch,velocity,500,undefined,prog,dest,undefined,se);}}}}}else if(cmd===0x8||cmd===0x9&&velocity===0){activeMidiPitchesRef.current.delete(pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));const current=lastMidiNoteRef.current;if(current&¤t.pitch===pitch){const lenSec=(performance.now()-current.startTime)/1000;lastMidiNoteRef.current={...current,length:lenSec};setLastMidiNote(prev=>prev&&prev.pitch===pitch?{...prev,length:lenSec,time:Date.now()}:prev);}}// Forward to active MIDI recorders
|
||||
const[localSelectionTrackId,setLocalSelectionTrackId]=useState(null);const[localSelectionStart,setLocalSelectionStart]=useState(null);const[localSelectionEnd,setLocalSelectionEnd]=useState(null);const[zoom,setZoom]=useState(100);const[isLoopingSelection,setIsLoopingSelection]=useState(false);const[beginBar,setBeginBar]=useState(0);const[endBar,setEndBar]=useState(0);const[numberBar,setNumberBar]=useState(1);const[subTabHeight,setSubTabHeight]=useState(96);const[isExporting,setIsExporting]=useState(false);const[projectName,setProjectName]=useState(()=>localStorage.getItem('sonic_project_name')||'');const[currentProjectId,setCurrentProjectId]=useState(()=>localStorage.getItem('sonic_project_id')||null);const[saveProjectModalOpen,setSaveProjectModalOpen]=useState(false);const[saveAsModalOpen,setSaveAsModalOpen]=useState(false);const soloedTrack=tracks.find(t=>t.solo);const soloedTrackId=soloedTrack?soloedTrack.id:null;const[toastMessage,setToastMessage]=useState(null);const[audioDevices,setAudioDevices]=useState([]);const[midiDevices,setMidiDevices]=useState([]);const[selectedMidiInputId,setSelectedMidiInputId]=useState('');const handleMidiInputSelect=id=>{setSelectedMidiInputId(id);if(window.SonicRecorderManager){window.SonicRecorderManager.setSelectedMidiInputId(id);}};useEffect(()=>{if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices){navigator.mediaDevices.enumerateDevices().then(devices=>{setAudioDevices(devices.filter(d=>d.kind==='audioinput'));}).catch(err=>console.log('Enumerate audio devices error:',err));}if(navigator.requestMIDIAccess){navigator.requestMIDIAccess().then(access=>{const inputs=[];for(let input of access.inputs.values()){inputs.push(input);input.onmidimessage=msg=>{console.log(`[DevLog] [Raw MIDI] Received from "${input.name}" (ID: ${input.id}) - Data:`,Array.from(msg.data));if(msg.data.length<3)return;const cmd=msg.data[0]>>4;const pitch=msg.data[1];const velocity=msg.data[2];if(cmd===0x9&&velocity>0){lastMidiNoteRef.current={pitch,velocity,startTime:performance.now(),length:0};setLastMidiNote({pitch,velocity,length:0,time:Date.now()});activeMidiPitchesRef.current.add(pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));{if(window.SonicSF){const arSub=subTabsRef&&subTabsRef.current&&activeTabRef&&subTabsRef.current.find(s=>s.id===activeTabRef.current&&s.type==='PIANO_ROLL'&&s.isArmed);if(arSub){window.SonicSF.playNote(pitch,velocity,60000,undefined,arSub.instrumentProgram,null,undefined,arSub.synth_engine);}else{const armedTrack=activeTracksRef.current?activeTracksRef.current.find(t=>t.isArmed):null;if(armedTrack){const prog=armedTrack.instrumentProgram;const se=armedTrack.synth_engine;const dest=activeTrackNodesRef.current[armedTrack.id]?.gainNode||null;window.SonicSF.playNote(pitch,velocity,60000,undefined,prog,dest,undefined,se);}}}}}else if(cmd===0x8||cmd===0x9&&velocity===0){activeMidiPitchesRef.current.delete(pitch);window.SonicSF.stopNote(msg.data[0]&0x0F,pitch);setActiveMidiPitches(new Set(activeMidiPitchesRef.current));const current=lastMidiNoteRef.current;if(current&¤t.pitch===pitch){const lenSec=(performance.now()-current.startTime)/1000;lastMidiNoteRef.current={...current,length:lenSec};setLastMidiNote(prev=>prev&&prev.pitch===pitch?{...prev,length:lenSec,time:Date.now()}:prev);}}// Forward to active MIDI recorders
|
||||
if(activeMIDIRecordersRef.current){for(let trackId in activeMIDIRecordersRef.current){const rec=activeMIDIRecordersRef.current[trackId];if(rec){rec.handleMIDIMessage(msg,input.id);}}}};}setMidiDevices(inputs);access.onstatechange=()=>{const inputs=[];for(let input of access.inputs.values()){inputs.push(input);}setMidiDevices(inputs);};}).catch(err=>console.log('MIDI access error:',err));}},[]);const[showAIConfig,setShowAIConfig]=useState(false);const[recordingState,setRecordingState]=useState('IDLE');// 'IDLE' | 'COUNT_IN' | 'RECORDING'
|
||||
const[recTempMidiNotes,setRecTempMidiNotes]=useState([]);const[recTempAudioBuffer,setRecTempAudioBuffer]=useState(null);const[recStartTimelineTime,setRecStartTimelineTime]=useState(0);const[canvasRedrawCount,setCanvasRedrawCount]=useState(0);const[lastMidiNote,setLastMidiNote]=useState(null);const lastMidiNoteRef=useRef(null);const activeMIDIRecordersRef=useRef({});const activeAudioRecordersRef=useRef({});const pianoRollRecorderRef=useRef(null);const activeMidiPitchesRef=useRef(new Set());const[activeMidiPitches,setActiveMidiPitches]=useState(new Set());const recordingPCMDataRef=useRef({});const recordingStartTimeRef=useRef(0);const recordingSyncRef=useRef(null);const recordingStateRef=useRef(recordingState);recordingStateRef.current=recordingState;const lastTempCompileTimeRef=useRef(0);const nextMetronomeBeatRef=useRef(0);const[showExportPanel,setShowExportPanel]=useState(false);const[showAIPanel,setShowAIPanel]=useState(true);const[showSelectionPanel,setShowSelectionPanel]=useState(false);const[showPythonToolsPanel,setShowPythonToolsPanel]=useState(false);const[showMediaExplorer,setShowMediaExplorer]=useState(false);const[scrollBufferExtra,setScrollBufferExtra]=useState(0);const scrollBufferExtraRef=useRef(0);scrollBufferExtraRef.current=scrollBufferExtra;const[showFxRack,setShowFxRack]=useState(false);const[showMidiEvents,setShowMidiEvents]=useState(false);const[rightSidebarWidth,setRightSidebarWidth]=useState(320);const[tcpWidth,setTcpWidth]=useState(320);const[mediaExplorerHeight,setMediaExplorerHeight]=useState(50);const[panelPositions,setPanelPositions]=useState({export:'bottom',ai:'right',python_tools:'bottom',selection:'bottom',media_explorer:'bottom',fx_rack:'bottom',midi_events:'bottom'});const[panelDropZone,setPanelDropZone]=useState(null);const[dragGhostPos,setDragGhostPos]=useState(null);const[dragGhostPanel,setDragGhostPanel]=useState(null);const panelDragRef=useRef(null);const trackVuRefs=useRef({});const workspaceRef=useRef(null);const colResizerRef=useRef(null);const rowResizerRef=useRef(null);const[aiConfig,setAiConfig]=useState({baseUrl:localStorage.getItem('ai_base_url')||`${API_BASE_URL}`,apiKey:localStorage.getItem('ai_api_key')||'',model:localStorage.getItem('ai_model')||'deepseek-chat'});const[aiProviders,setAiProviders]=useState([]);useEffect(()=>{(async()=>{try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){/* server may not have config endpoint */}})();},[]);const[analysisState,setAnalysisState]=useState({status:'Sẵn sàng. Chạy AI để phân tích nhịp.',data:null,isRunning:false});const[aiPrompt,setAiPrompt]=useState('');const[promptHistory,setPromptHistory]=useState([]);const[promptHistIdx,setPromptHistIdx]=useState(-1);const promptHistRef=useRef([]);const[aiProvider,setAiProvider]=useState('OpenAI');const[aiModel,setAiModel]=useState('GPT-4o');const[aiActionLog,setAiActionLog]=useState([]);const actionLogContainerRef=useRef(null);useEffect(()=>{if(actionLogContainerRef.current){actionLogContainerRef.current.scrollTop=actionLogContainerRef.current.scrollHeight;}},[aiActionLog]);const[aiProcessing,setAiProcessing]=useState(false);const[selectedProviderId,setSelectedProviderId]=useState('');const[exportSettings,setExportSettings]=useState({sampleRate:'44100',bitDepth:'16',format:'wav',source:'project',quality:'44khz',channels:'stereo'});const[serverStatus,setServerStatus]=useState('checking...');const[menuOpen,setMenuOpen]=useState(null);const[selectedClipId,setSelectedClipId]=useState(null);// { trackId, clipId }
|
||||
const[stretchedClip,setStretchedClip]=useState(null);// { trackId, clipId, originalDuration, startTime, originalSpeed, beforeSnap }
|
||||
|
||||
Reference in New Issue
Block a user