FIX: 3 bugs - soundfont engine fallback, media explorer icons/drag, carla midi playback routing
- render_engine: soundfont track không có plugin_id giờ vẫn dùng instrument sf2 thay vì rơi về wave synth - fontawesome: sửa đường dẫn webfonts (bỏ ../) hết 404 icon - media explorer: drag MIDI library items vào timeline tạo được midi item (sinh 6 MIDI loop mẫu + resolve drop file) - app.jsx: 3 path gate window.SonicSF cho phép route playback qua SonicCarlaMidi
This commit is contained in:
+11
-4
@@ -21306,13 +21306,13 @@ const App = () => {
|
||||
|
||||
// MIDI items playback
|
||||
const midiItems = track.midiItems || [];
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && window.SonicSF) {
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)))) {
|
||||
// Preview cache: capture the soundfont (pre track-FX) for fast offline export
|
||||
ensureMidiCapture(track, activeTrackNodesRef.current[track.id]);
|
||||
if (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)) ensureCarlaForPlayback(track.synth_engine);
|
||||
var trkCh = assignTrackMidiChannel(track, allPlayTracks);
|
||||
// Ensure instrument is loaded in FluidSynth
|
||||
if (track.synth_engine && track.synth_engine.type === 'soundfont' && track.synth_engine.soundfont_id) {
|
||||
if (window.SonicSF && 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;
|
||||
@@ -21464,7 +21464,7 @@ const App = () => {
|
||||
|
||||
// 2. Play MIDI items in subTrack
|
||||
const subMidiItems = subTrack.midiItems || [];
|
||||
if (window.SonicSF && subMidiItems.length > 0) {
|
||||
if (subMidiItems.length > 0 && (window.SonicSF || (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine)))) {
|
||||
subMidiItems.forEach(item => {
|
||||
const notes = item.notes || [];
|
||||
notes.forEach(note => {
|
||||
@@ -21592,7 +21592,7 @@ const App = () => {
|
||||
|
||||
// MIDI items playback
|
||||
const midiItems = track.midiItems || [];
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && window.SonicSF) {
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)))) {
|
||||
var lcCh = assignTrackMidiChannel(track, tracks);
|
||||
const bpmVal = parseInt(bpm) || 120;
|
||||
const secondsPerBeat = 60.0 / bpmVal;
|
||||
@@ -23905,6 +23905,13 @@ const App = () => {
|
||||
const blob = await resp.blob();
|
||||
return new File([blob], mef.name || mef.path.split(/[\\/]/).pop(), { type: blob.type || 'application/octet-stream' });
|
||||
}
|
||||
// Media Library samples: only metadata (name/kind) — real bytes served
|
||||
// from /static/midi (generated demo loops, see app/static/midi).
|
||||
if (mef.kind === 'midi' && mef.name) {
|
||||
const resp = await fetch(`${API_BASE_URL}/static/midi/${encodeURIComponent(mef.name)}`);
|
||||
const blob = await resp.blob();
|
||||
return new File([blob], mef.name, { type: 'audio/midi' });
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Không thể nạp file từ Media Explorer: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
@@ -1371,9 +1371,9 @@ scheduledItemsSigRef.current=buildItemsSignature(allPlayTracks,sessionTabs);cons
|
||||
// → first play sau double-click vào SECTION-TAB bị bypass. Re-apply
|
||||
// routeBypassFor(track) mỗi lần schedule sẽ hết chu kỳ "stop+play mới đúng".
|
||||
const playNode=activeTrackNodesRef.current[track.id];if(playNode&&playNode.route&&playNode.route.routeGain){const rb=routeBypassFor(track);playNode.route.routeGain.gain.value=rb?0:1;playNode.route.dryGain.gain.value=rb?1:0;console.log('[Bypass] reapply track',track.id,'contentType='+(routeBypassFor._lastType?'midi':'audio'),'bypass='+rb,'→ routeGain='+playNode.route.routeGain.gain.value+'/dryGain='+playNode.route.dryGain.gain.value);}console.log('[Play] track',track.id,track.name,'node ok:',!!(gainNode&&pannerNode),'muted:',track.muted);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}]:[];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);const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime<clipStart){const delay=clipStart-offsetTime;source.start(context.currentTime+delay,0);activeSourcesRef.current.push(source);}else if(offsetTime<clipEnd){const playOffset=offsetTime-clipStart;source.start(context.currentTime,playOffset*(clip.speed||1.0));activeSourcesRef.current.push(source);}});// MIDI items playback
|
||||
const midiItems=track.midiItems||[];if((track.type==='MIDI'||midiItems.length>0)&&window.SonicSF){// Preview cache: capture the soundfont (pre track-FX) for fast offline export
|
||||
const midiItems=track.midiItems||[];if((track.type==='MIDI'||midiItems.length>0)&&(window.SonicSF||window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine))){// Preview cache: capture the soundfont (pre track-FX) for fast offline export
|
||||
ensureMidiCapture(track,activeTrackNodesRef.current[track.id]);if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine))ensureCarlaForPlayback(track.synth_engine);var trkCh=assignTrackMidiChannel(track,allPlayTracks);// Ensure instrument is loaded in FluidSynth
|
||||
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)
|
||||
if(window.SonicSF&&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;// Track VSTi + Carla local → MIDI item PHẢI play qua Carla bridge
|
||||
// (chỉ route Carla — KHÔNG play qua FluidSynth GM nữa, tránh âm
|
||||
@@ -1398,7 +1398,7 @@ const sections=track.sections||[];if(activeTab==='main'&§ions.length>0){cons
|
||||
const subNodeObj=activeTrackNodesRef.current[track.id+'_sub_'+subTrack.id];if(subNodeObj&&subNodeObj.route&&subNodeObj.route.routeGain){const srb=routeBypassFor(subTrack);subNodeObj.route.routeGain.gain.value=srb?0:1;subNodeObj.route.dryGain.gain.value=srb?1:0;}// 1. Play clips in subTrack
|
||||
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
|
||||
const subMidiItems=subTrack.midiItems||[];if(subMidiItems.length>0&&(window.SonicSF||window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine))){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);// MIDI item trong SECTION → Carla bridge khi VSTi loaded
|
||||
// (chỉ route Carla — không play FluidSynth GM sai âm)
|
||||
const subRouteCarla=!!(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine));if(subRouteCarla||window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine))ensureCarlaForPlayback(subTrack.synth_engine);if(offsetTime<noteStartMain){const delay=noteStartMain-offsetTime;const startTime=context.currentTime+delay;const playDurMs=(notePlayEndMain-noteStartMain)*1000;if(!subRouteCarla&&window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,playDurMs,startTime,program,subNode,subCh,subTrack.synth_engine);}if(subRouteCarla)scheduleCarlaNote(subTrack.synth_engine,subCh,note.pitch||60,note.velocity||0.8,startTime,playDurMs);// Trigger VU meter flash đúng nhịp (cả subTrack — VU
|
||||
@@ -1417,7 +1417,7 @@ const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackN
|
||||
// chorus/reverb → pannerNode. A direct tap would BYPASS the FX Rack
|
||||
// chain for audio clips (double path: with + without FX).
|
||||
const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime<clipStart){const delay=clipStart-offsetTime;source.start(context.currentTime+delay,0);activeSourcesRef.current.push(source);}else if(offsetTime<clipEnd){const playOffset=offsetTime-clipStart;source.start(context.currentTime,playOffset*(clip.speed||1.0));activeSourcesRef.current.push(source);}});// MIDI items playback
|
||||
const midiItems=track.midiItems||[];if((track.type==='MIDI'||midiItems.length>0)&&window.SonicSF){var lcCh=assignTrackMidiChannel(track,tracks);const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime<noteEndSec){const durationMs=noteDurSec*1000;const program=track.instrumentProgram!==undefined?track.instrumentProgram:0;// MIDI item → Carla bridge khi VSTi loaded (chỉ route Carla — không
|
||||
const midiItems=track.midiItems||[];if((track.type==='MIDI'||midiItems.length>0)&&(window.SonicSF||window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine))){var lcCh=assignTrackMidiChannel(track,tracks);const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime<noteEndSec){const durationMs=noteDurSec*1000;const program=track.instrumentProgram!==undefined?track.instrumentProgram:0;// MIDI item → Carla bridge khi VSTi loaded (chỉ route Carla — không
|
||||
// play FluidSynth GM sai âm chồng lên)
|
||||
const routeToCarla=!!(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine));if(offsetTime<noteStartSec){const delay=noteStartSec-offsetTime;const startTime=context.currentTime+delay;if(!routeToCarla&&window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durationMs,startTime,program,gainNode,lcCh,track.synth_engine);}// MIDI items → Carla bridge (track VSTi + Carla local)
|
||||
if(routeToCarla)scheduleCarlaNote(track.synth_engine,lcCh,note.pitch||60,note.velocity||0.8,startTime,durationMs);}else{const remainingDurMs=(noteEndSec-offsetTime)*1000;if(!routeToCarla&&window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,gainNode,lcCh,track.synth_engine);}// MIDI items → Carla bridge (track VSTi + Carla local)
|
||||
@@ -1508,7 +1508,9 @@ const nodes=activeTrackNodesRef.current[trackId];if(nodes){nodes.pannerNode.pan.
|
||||
try{if(window.SonicStorage){const nextTracks=(tracks||[]).map(t=>t.id===trackId?{...t,color}:t);window.SonicStorage.scheduleTempAutoSave(()=>serializeProjectToSchema(currentProjectId||'temp_project',projectName||'Dự án tạm chưa lưu',bpm,nextTracks,subTabs,sessionTabs,masteringSettings));if(window.SonicStorage.flushTempAutoSave)window.SonicStorage.flushTempAutoSave();}}catch(e){}setUndoStack(prev=>{const next=[...prev,{action_type:'RECOLOR',track_id:trackId,timestamp:Date.now(),before_state:beforeSnap,after_state:captureTrackSnapshot(trackId)}];if(next.length>MAX_UNDO)next.shift();return next;});};const updateClipName=(trackId,clipId,newName)=>{setTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const clips=t.clips&&t.clips.length>0?t.clips:[];return{...t,clips:clips.map(c=>c.id===clipId?{...c,name:newName}:c)};}));};// ── MIDI .mid file parser ──
|
||||
const parseMidiFile=arrayBuffer=>{const data=new Uint8Array(arrayBuffer);if(data.length<14)return null;var pos=0;var read32=function(){var v=data[pos]<<24|data[pos+1]<<16|data[pos+2]<<8|data[pos+3];pos+=4;return v;};var read16=function(){var v=data[pos]<<8|data[pos+1];pos+=2;return v;};var readVLQ=function(){var v=0,b;do{b=data[pos++];v=v<<7|b&0x7f;}while(b&0x80);return v;};var header=String.fromCharCode(data[0],data[1],data[2],data[3]);if(header!=='MThd')return null;pos=8;var fmt=read16();var numTracks=read16();var division=read16();var ticksPerBeat=division&0x8000?480:division||480;var bpm=120;var result=[];for(var t=0;t<numTracks;t++){if(pos+8>data.length)break;var trkId=String.fromCharCode(data[pos],data[pos+1],data[pos+2],data[pos+3]);pos+=4;var trkLen=read32();var endPos=Math.min(pos+trkLen,data.length);if(trkId!=='MTrk'){pos=endPos;continue;}var absTicks=0;var runningStatus=0;var trackName='MIDI Track '+(t+1);var midiNotes=[];var pendingNotes={};var maxAbsTick=0;while(pos<endPos){try{var delta=readVLQ();absTicks+=delta;var status=data[pos];if(status>=0x80){if(status<0xf0){runningStatus=status;}pos++;}else{status=runningStatus;}var cmd=status>>4;if(cmd===0x9||cmd===0x8){var chan=status&0x0F;var pitch=data[pos++];var vel=pos<endPos?data[pos++]:0;var noteKey=chan+'_'+pitch;if(cmd===0x9&&vel>0){pendingNotes[noteKey]={tick:absTicks,vel:vel};if(absTicks>maxAbsTick)maxAbsTick=absTicks;}else{var pn=pendingNotes[noteKey];if(pn){var durTicks=absTicks-pn.tick;if(durTicks<=0)durTicks=240;midiNotes.push({id:'mn_'+t+'_'+pitch+'_'+pn.tick,pitch:pitch,start_beat:pn.tick/ticksPerBeat,duration_beats:durTicks/ticksPerBeat,velocity:Math.min(1,pn.vel/127)});delete pendingNotes[noteKey];}}}else if(status>=0xc0&&status<0xe0){if(pos<endPos)pos+=1;}else if(status>=0xe0&&status<0xf0){if(pos+2<=endPos)pos+=2;}else if(status>=0xa0&&status<0xc0){if(pos+2<=endPos)pos+=2;}else if(status>=0xf0&&status<0xf8){if(pos<endPos){var sl=readVLQ();pos+=sl;}}else if(status===0xff){if(pos>=endPos)break;var metaType=data[pos++];var metaLen=readVLQ();if(metaType===0x03){try{trackName=String.fromCharCode.apply(null,Array.from(data.subarray(pos,pos+metaLen)));}catch(e){}}else if(metaType===0x51&&pos+3<=endPos){bpm=Math.round(60000000/(data[pos]<<16|data[pos+1]<<8|data[pos+2]));}pos+=Math.min(metaLen,endPos-pos);}else{if(pos+2<=endPos)pos+=2;}}catch(e){pos=endPos;}}Object.keys(pendingNotes).forEach(function(k){var pn=pendingNotes[k];var parts=k.split('_');var p=parseInt(parts[1]);var dur=Math.max(240,maxAbsTick-pn.tick);midiNotes.push({id:'mn_'+t+'_'+p+'_'+pn.tick,pitch:p,start_beat:pn.tick/ticksPerBeat,duration_beats:dur/ticksPerBeat,velocity:Math.min(1,pn.vel/127)});});if(midiNotes.length>0){var lastEnd=0;var maxEndBeat=0;midiNotes.forEach(function(n){var e=(n.start_beat+n.duration_beats)*60/bpm;if(e>lastEnd)lastEnd=e;var eb=n.start_beat+n.duration_beats;if(eb>maxEndBeat)maxEndBeat=eb;});result.push({name:trackName,notes:midiNotes,duration:lastEnd||4,startTime:0,id:'midi_'+t+'_'+Date.now(),totalBeats:maxEndBeat||16,bars:Math.max(1,Math.ceil((maxEndBeat||16)/4)),bpm:bpm,ticksPerBeat:ticksPerBeat});}pos=endPos;}return result.length>0?result:null;};window.parseMidiFile=parseMidiFile;// ── Load File on Track (with server upload) ──
|
||||
// ── Resolve Media Explorer drag file → real File (for timeline drop) ──
|
||||
const resolveMediaExplorerDropFile=async()=>{const mef=window.__mediaExplorerDragFile;if(!mef)return null;try{if(mef.handle&&typeof mef.handle.getFile==='function'){const f=await mef.handle.getFile();return new File([f],f.name||mef.name,{type:f.type||'application/octet-stream'});}if(mef.file_id||mef.fileId){const fid=mef.file_id||mef.fileId;const resp=await fetch(`${API_BASE_URL}/api/v1/audio/download/${fid}`);const blob=await resp.blob();return new File([blob],mef.name||mef.original_name||fid,{type:blob.type||'audio/wav'});}if(mef.path){const resp=await fetch(`${API_BASE_URL}/api/v1/media/file?path=${encodeURIComponent(mef.path)}`);const blob=await resp.blob();return new File([blob],mef.name||mef.path.split(/[\\/]/).pop(),{type:blob.type||'application/octet-stream'});}}catch(e){showToast('Không thể nạp file từ Media Explorer: '+e.message,'error');}return null;};const loadFileOnTrack=async(trackId,file)=>{if(!file)return;var fileName=file.name||'';var isMidi=/\.mid$|\.midi$/i.test(fileName);showToast(`Đang nạp file ${fileName}...`,'info');try{if(isMidi){var arrayBuffer=await file.arrayBuffer();var midiResult=parseMidiFile(arrayBuffer);if(!midiResult||midiResult.length===0){showToast('Không tìm thấy nốt nhạc trong file MIDI.','error');return;}if(midiResult.length===1){updateActiveTracks(prev=>prev.map(function(t){if(t.id!==trackId)return t;return{...t,name:fileName,midiItems:[midiResult[0]]};}));showToast('Đã tải MIDI: '+fileName,'success');}else{var colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309','#ca8a04','#dc2626','#0891b2'];var firstDone=false;midiResult.forEach(function(midiItem,idx){if(!firstDone){updateActiveTracks(prev=>prev.map(function(t){if(t.id!==trackId)return t;return{...t,name:midiItem.name||fileName,midiItems:[midiItem]};}));firstDone=true;}else{var newId='midi_track_'+Date.now()+'_'+idx;updateActiveTracks(function(prev){var curLen=prev.length;return prev.concat([{id:newId,name:midiItem.name||'MIDI Track '+(idx+1),buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:colors[idx%colors.length],markers:[],serverFileId:null,clips:[],sections:[],midiItems:[midiItem],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);});}});showToast('Đã tải MIDI: '+midiResult.length+' tracks from '+fileName,'success');}return;}// Upload to server
|
||||
const resolveMediaExplorerDropFile=async()=>{const mef=window.__mediaExplorerDragFile;if(!mef)return null;try{if(mef.handle&&typeof mef.handle.getFile==='function'){const f=await mef.handle.getFile();return new File([f],f.name||mef.name,{type:f.type||'application/octet-stream'});}if(mef.file_id||mef.fileId){const fid=mef.file_id||mef.fileId;const resp=await fetch(`${API_BASE_URL}/api/v1/audio/download/${fid}`);const blob=await resp.blob();return new File([blob],mef.name||mef.original_name||fid,{type:blob.type||'audio/wav'});}if(mef.path){const resp=await fetch(`${API_BASE_URL}/api/v1/media/file?path=${encodeURIComponent(mef.path)}`);const blob=await resp.blob();return new File([blob],mef.name||mef.path.split(/[\\/]/).pop(),{type:blob.type||'application/octet-stream'});}// Media Library samples: only metadata (name/kind) — real bytes served
|
||||
// from /static/midi (generated demo loops, see app/static/midi).
|
||||
if(mef.kind==='midi'&&mef.name){const resp=await fetch(`${API_BASE_URL}/static/midi/${encodeURIComponent(mef.name)}`);const blob=await resp.blob();return new File([blob],mef.name,{type:'audio/midi'});}}catch(e){showToast('Không thể nạp file từ Media Explorer: '+e.message,'error');}return null;};const loadFileOnTrack=async(trackId,file)=>{if(!file)return;var fileName=file.name||'';var isMidi=/\.mid$|\.midi$/i.test(fileName);showToast(`Đang nạp file ${fileName}...`,'info');try{if(isMidi){var arrayBuffer=await file.arrayBuffer();var midiResult=parseMidiFile(arrayBuffer);if(!midiResult||midiResult.length===0){showToast('Không tìm thấy nốt nhạc trong file MIDI.','error');return;}if(midiResult.length===1){updateActiveTracks(prev=>prev.map(function(t){if(t.id!==trackId)return t;return{...t,name:fileName,midiItems:[midiResult[0]]};}));showToast('Đã tải MIDI: '+fileName,'success');}else{var colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309','#ca8a04','#dc2626','#0891b2'];var firstDone=false;midiResult.forEach(function(midiItem,idx){if(!firstDone){updateActiveTracks(prev=>prev.map(function(t){if(t.id!==trackId)return t;return{...t,name:midiItem.name||fileName,midiItems:[midiItem]};}));firstDone=true;}else{var newId='midi_track_'+Date.now()+'_'+idx;updateActiveTracks(function(prev){var curLen=prev.length;return prev.concat([{id:newId,name:midiItem.name||'MIDI Track '+(idx+1),buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:colors[idx%colors.length],markers:[],serverFileId:null,clips:[],sections:[],midiItems:[midiItem],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);});}});showToast('Đã tải MIDI: '+midiResult.length+' tracks from '+fileName,'success');}return;}// Upload to server
|
||||
uploadToServer(file,trackId);// Decode locally for playback + analyze channels (stereo/mono)
|
||||
const{audioBuffer:decodedBuffer,channelInfo}=await window.SonicAudio.decodeAudioFile(file);setTracks(prev=>prev.map(t=>t.id===trackId?{...t,name:fileName,buffer:decodedBuffer,channelInfo:channelInfo}:t));showToast(`Nạp file thành công: ${fileName} (${channelInfo.label})`,'success');}catch(err){showToast(isMidi?"Lỗi giải mã MIDI.":"Lỗi giải mã âm thanh. Định dạng file không tương thích.",'error');}};// ── Drop MIDI file → create new track(s) ──
|
||||
const handleDropMidiToNewTracks=async file=>{if(!file)return;showToast('Đang nạp MIDI: '+file.name+'...','info');try{var arrayBuffer=await file.arrayBuffer();var midiResult=parseMidiFile(arrayBuffer);if(!midiResult||midiResult.length===0){showToast('Không tìm thấy nốt nhạc trong file MIDI.','error');return;}var colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309','#ca8a04','#dc2626','#0891b2'];var now=Date.now();var newTracks=midiResult.map(function(midiItem,idx){return{id:'midi_track_'+now+'_'+idx,name:midiItem.name||(midiResult.length>1?'MIDI Track '+(idx+1):(file.name||'MIDI').replace(/\.midi?$/i,'')),buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,masteringBypass:false,audioBypass:false,midiBypass:false,fxActive:true,color:colors[idx%colors.length],markers:[],serverFileId:null,clips:[],sections:[],midiItems:[midiItem],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}};});updateActiveTracks(function(prev){return prev.concat(newTracks);});if(newTracks.length>0)setSelectedTrackId(newTracks[0].id);showToast('Đã tải MIDI: '+newTracks.length+' track(s) từ '+file.name,'success');}catch(err){showToast('Lỗi giải mã MIDI.','error');}};// ── Synth Generators ──
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user