LAN access: bind 0.0.0.0 + expose LAN URL (capabilities.lan, UI badge)
This commit is contained in:
@@ -12,6 +12,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -326,6 +327,29 @@ def default_soundfont_dirs() -> list:
|
|||||||
return ["/opt/daw_engine/soundfonts", os.path.expanduser("~/.sf2")]
|
return ["/opt/daw_engine/soundfonts", os.path.expanduser("~/.sf2")]
|
||||||
|
|
||||||
|
|
||||||
|
def _lan_ips() -> list:
|
||||||
|
"""IPv4 của máy trên mạng LAN (bỏ loopback) — để client browser ở máy
|
||||||
|
khác mở app qua địa chỉ này. Stdlib only (không kéo thư viện ngoài)."""
|
||||||
|
ips = []
|
||||||
|
try:
|
||||||
|
# IP ra mạng theo default route — không gửi gói tin thật
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
s.connect(("8.8.8.8", 80))
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
if ip and not ip.startswith("127.") and ip not in ips:
|
||||||
|
ips.append(ip)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Enumerate thêm các interface khác (multi-NIC)
|
||||||
|
try:
|
||||||
|
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
|
||||||
|
ip = info[4][0]
|
||||||
|
if ip and not ip.startswith("127.") and ip not in ips:
|
||||||
|
ips.append(ip)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ips
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=1)
|
@functools.lru_cache(maxsize=1)
|
||||||
def detect() -> dict:
|
def detect() -> dict:
|
||||||
"""Detect 1 lần (cache toàn cục) — kết quả bất biến trong 1 tiến trình."""
|
"""Detect 1 lần (cache toàn cục) — kết quả bất biến trong 1 tiến trình."""
|
||||||
@@ -359,6 +383,20 @@ def _vst_render_available() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _lan_info() -> dict:
|
||||||
|
"""Địa chỉ + port để client LAN mở app. Port: SF_PORT (desktop engine chọn
|
||||||
|
trước, 8000-8010) hoặc 8000 mặc định."""
|
||||||
|
try:
|
||||||
|
port = int(os.environ.get("SF_PORT") or "8000")
|
||||||
|
except ValueError:
|
||||||
|
port = 8000
|
||||||
|
ips = _lan_ips()
|
||||||
|
return {
|
||||||
|
"ips": ips,
|
||||||
|
"port": port,
|
||||||
|
"urls": [f"http://{ip}:{port}" for ip in ips],
|
||||||
|
}
|
||||||
|
|
||||||
def capabilities() -> dict:
|
def capabilities() -> dict:
|
||||||
"""Capabilities API — frontend gọi 1 lần lúc boot để bật/tắt tính năng."""
|
"""Capabilities API — frontend gọi 1 lần lúc boot để bật/tắt tính năng."""
|
||||||
d = detect()
|
d = detect()
|
||||||
@@ -378,6 +416,9 @@ def capabilities() -> dict:
|
|||||||
"platform": d["platform"],
|
"platform": d["platform"],
|
||||||
"docker": d["docker"],
|
"docker": d["docker"],
|
||||||
"environment": d["environment"],
|
"environment": d["environment"],
|
||||||
|
# LAN access (chỉ standalone): URL để client browser ở máy khác trên
|
||||||
|
# mạng mở app. Docker (container) → bỏ qua (IP container không dùng được).
|
||||||
|
**({"lan": _lan_info()} if not d["docker"] else {}),
|
||||||
"features": features,
|
"features": features,
|
||||||
"default_dirs": {
|
"default_dirs": {
|
||||||
"vst": d["default_vst_dirs"],
|
"vst": d["default_vst_dirs"],
|
||||||
|
|||||||
@@ -15171,6 +15171,36 @@ const App = () => {
|
|||||||
})
|
})
|
||||||
})));
|
})));
|
||||||
}, [bpm]);
|
}, [bpm]);
|
||||||
|
// LAN access badge (standalone): server bind 0.0.0.0 — client browser ở máy
|
||||||
|
// khác trên mạng mở app qua http://<LAN-IP>:port. Hiện URL trên máy chủ;
|
||||||
|
// client LAN thấy origin đang dùng.
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
let bar = null;
|
||||||
|
let iv = null;
|
||||||
|
const tryShow = () => {
|
||||||
|
const caps = window.SonicRuntime && window.SonicRuntime.capabilities;
|
||||||
|
const lan = caps && caps.lan;
|
||||||
|
if (!lan || !lan.urls || !lan.urls.length) return false;
|
||||||
|
if (document.getElementById('sf-lan-bar')) return true;
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
||||||
|
bar = document.createElement('div');
|
||||||
|
bar.id = 'sf-lan-bar';
|
||||||
|
bar.style.cssText = 'position:fixed;top:8px;right:12px;z-index:9999;background:rgba(16,185,129,0.12);border:1px solid #10b981;color:#a7f3d0;font-size:11px;line-height:1.5;padding:6px 10px;border-radius:8px;max-width:min(70vw,420px);cursor:default;';
|
||||||
|
bar.title = 'Client browser ở máy khác trên LAN mở địa chỉ này để làm việc realtime với máy chạy SonicForge';
|
||||||
|
bar.innerHTML = isLocal
|
||||||
|
? '<b>LAN:</b> ' + lan.urls.map(function (u) { return '<a href="' + u + '" target="_blank" style="color:#a7f3d0;text-decoration:underline">' + u + '</a>'; }).join(' · ')
|
||||||
|
: '<b>Kết nối:</b> ' + window.location.origin;
|
||||||
|
document.body.appendChild(bar);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!tryShow()) {
|
||||||
|
iv = setInterval(function () { if (mounted && tryShow()) clearInterval(iv); }, 700);
|
||||||
|
setTimeout(function () { if (iv) clearInterval(iv); }, 20000);
|
||||||
|
}
|
||||||
|
return () => { mounted = false; if (iv) clearInterval(iv); if (bar && bar.parentNode) bar.parentNode.removeChild(bar); };
|
||||||
|
}, []);
|
||||||
const openPanel = id => {
|
const openPanel = id => {
|
||||||
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
|
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -754,7 +754,10 @@ useEffect(()=>{const oldSpb=prevBpmRef.current?60.0/parseFloat(prevBpmRef.curren
|
|||||||
if(oldSpb&&selectionFollowsTempo&&selectionStart!==null&&selectionEnd!==null&&selectionEnd>selectionStart){const startBar=selectionStart/oldSpb;const endBar=selectionEnd/oldSpb;if(endBar-startBar>0.01){setSelectionStart(startBar*secondsPerBar);setSelectionEnd(endBar*secondsPerBar);}}prevBpmRef.current=bpm;// Force canvas redraw
|
if(oldSpb&&selectionFollowsTempo&&selectionStart!==null&&selectionEnd!==null&&selectionEnd>selectionStart){const startBar=selectionStart/oldSpb;const endBar=selectionEnd/oldSpb;if(endBar-startBar>0.01){setSelectionStart(startBar*secondsPerBar);setSelectionEnd(endBar*secondsPerBar);}}prevBpmRef.current=bpm;// Force canvas redraw
|
||||||
setCanvasRedrawCount(n=>n+1);// Recalculate item/section durations
|
setCanvasRedrawCount(n=>n+1);// Recalculate item/section durations
|
||||||
updateActiveTracks(prev=>prev.map(t=>({...t,midiItems:(t.midiItems||[]).map(m=>{if(m.length_bars)return{...m,duration:m.length_bars*secondsPerBar};if(m.duration){// Legacy item without length_bars: compute bars from current duration/BPM
|
updateActiveTracks(prev=>prev.map(t=>({...t,midiItems:(t.midiItems||[]).map(m=>{if(m.length_bars)return{...m,duration:m.length_bars*secondsPerBar};if(m.duration){// Legacy item without length_bars: compute bars from current duration/BPM
|
||||||
const bars=Math.max(0.25,Math.round(m.duration/secondsPerBar*4)/4);return{...m,length_bars:bars,duration:bars*secondsPerBar};}return m;}),sections:(t.sections||[]).map(s=>{if(s.length_bars)return{...s,duration:s.length_bars*secondsPerBar};if(s.duration){const bars=Math.max(0.25,Math.round(s.duration/secondsPerBar*4)/4);return{...s,length_bars:bars,duration:bars*secondsPerBar};}return s;})})));},[bpm]);const openPanel=id=>{if(id==='export')setShowExportPanel(true);else if(id==='ai')setShowAIPanel(true);else if(id==='python_tools')setShowPythonToolsPanel(true);else if(id==='selection')setShowSelectionPanel(true);};const[instrumentSelectorTrackId,setInstrumentSelectorTrackId]=useState(null);const[synthTrackDropdownId,setSynthTrackDropdownId]=useState(null);const[fxSelectorTrackId,setFxSelectorTrackId]=useState(null);const handleSetTrackFx=(trackId,fxType)=>{updateActiveTracks(prev=>prev.map(t=>t.id===trackId?{...t,fxType}:t));setFxSelectorTrackId(null);setTimeout(()=>lucide.createIcons(),50);};const[instrumentSelectorData,setInstrumentSelectorData]=useState(null);const[instrumentRefreshKey,setInstrumentRefreshKey]=useState(0);const openInstrumentSelector=trackId=>{setInstrumentSelectorTrackId(trackId);setSfPresetSearchQuery('');setInstrumentRefreshKey(k=>k+1);setSfPresets(null);// Fetch instruments fresh from API for all soundfonts
|
const bars=Math.max(0.25,Math.round(m.duration/secondsPerBar*4)/4);return{...m,length_bars:bars,duration:bars*secondsPerBar};}return m;}),sections:(t.sections||[]).map(s=>{if(s.length_bars)return{...s,duration:s.length_bars*secondsPerBar};if(s.duration){const bars=Math.max(0.25,Math.round(s.duration/secondsPerBar*4)/4);return{...s,length_bars:bars,duration:bars*secondsPerBar};}return s;})})));},[bpm]);// LAN access badge (standalone): server bind 0.0.0.0 — client browser ở máy
|
||||||
|
// khác trên mạng mở app qua http://<LAN-IP>:port. Hiện URL trên máy chủ;
|
||||||
|
// client LAN thấy origin đang dùng.
|
||||||
|
useEffect(()=>{let mounted=true;let bar=null;let iv=null;const tryShow=()=>{const caps=window.SonicRuntime&&window.SonicRuntime.capabilities;const lan=caps&&caps.lan;if(!lan||!lan.urls||!lan.urls.length)return false;if(document.getElementById('sf-lan-bar'))return true;const hostname=window.location.hostname;const isLocal=hostname==='localhost'||hostname==='127.0.0.1'||hostname==='::1';bar=document.createElement('div');bar.id='sf-lan-bar';bar.style.cssText='position:fixed;top:8px;right:12px;z-index:9999;background:rgba(16,185,129,0.12);border:1px solid #10b981;color:#a7f3d0;font-size:11px;line-height:1.5;padding:6px 10px;border-radius:8px;max-width:min(70vw,420px);cursor:default;';bar.title='Client browser ở máy khác trên LAN mở địa chỉ này để làm việc realtime với máy chạy SonicForge';bar.innerHTML=isLocal?'<b>LAN:</b> '+lan.urls.map(function(u){return'<a href="'+u+'" target="_blank" style="color:#a7f3d0;text-decoration:underline">'+u+'</a>';}).join(' · '):'<b>Kết nối:</b> '+window.location.origin;document.body.appendChild(bar);return true;};if(!tryShow()){iv=setInterval(function(){if(mounted&&tryShow())clearInterval(iv);},700);setTimeout(function(){if(iv)clearInterval(iv);},20000);}return()=>{mounted=false;if(iv)clearInterval(iv);if(bar&&bar.parentNode)bar.parentNode.removeChild(bar);};},[]);const openPanel=id=>{if(id==='export')setShowExportPanel(true);else if(id==='ai')setShowAIPanel(true);else if(id==='python_tools')setShowPythonToolsPanel(true);else if(id==='selection')setShowSelectionPanel(true);};const[instrumentSelectorTrackId,setInstrumentSelectorTrackId]=useState(null);const[synthTrackDropdownId,setSynthTrackDropdownId]=useState(null);const[fxSelectorTrackId,setFxSelectorTrackId]=useState(null);const handleSetTrackFx=(trackId,fxType)=>{updateActiveTracks(prev=>prev.map(t=>t.id===trackId?{...t,fxType}:t));setFxSelectorTrackId(null);setTimeout(()=>lucide.createIcons(),50);};const[instrumentSelectorData,setInstrumentSelectorData]=useState(null);const[instrumentRefreshKey,setInstrumentRefreshKey]=useState(0);const openInstrumentSelector=trackId=>{setInstrumentSelectorTrackId(trackId);setSfPresetSearchQuery('');setInstrumentRefreshKey(k=>k+1);setSfPresets(null);// Fetch instruments fresh from API for all soundfonts
|
||||||
window.SonicAPI.listPlugins().then(async data=>{var sfonts=data.soundfonts||[];if(sfonts.length===0){setSfPresets([]);return;}try{var results=await Promise.all(sfonts.map(function(sf){var sfId=sf.id.replace('sf_','');return window.SonicAPI.listSoundfontInstruments(sfId).then(function(r){return{sf:sf,presets:r.presets||[]};}).catch(function(){return{sf:sf,presets:[]};});}));var all=[];results.forEach(function(r){var sf=r.sf;var sfId=sf.id.startsWith('sf_')?sf.id:'sf_'+sf.id;var sfName=sf.display||sf.name||sf.id;(r.presets||[]).forEach(function(p){all.push({...p,_sfId:sfId,_sfName:sfName,_sfDisplay:sfName.substring(0,30)});});});setSfPresets(all.length>0?all:[]);}catch(e){setSfPresets([]);}}).catch(function(){setSfPresets([]);});};const closeInstrumentSelector=()=>{setInstrumentSelectorTrackId(null);setSynthCategory(null);setSelectedSoundFontId(null);setSfPresetSearchQuery('');};const[synthCategory,setSynthCategory]=useState(null);// 'vst' | 'soundfont'
|
window.SonicAPI.listPlugins().then(async data=>{var sfonts=data.soundfonts||[];if(sfonts.length===0){setSfPresets([]);return;}try{var results=await Promise.all(sfonts.map(function(sf){var sfId=sf.id.replace('sf_','');return window.SonicAPI.listSoundfontInstruments(sfId).then(function(r){return{sf:sf,presets:r.presets||[]};}).catch(function(){return{sf:sf,presets:[]};});}));var all=[];results.forEach(function(r){var sf=r.sf;var sfId=sf.id.startsWith('sf_')?sf.id:'sf_'+sf.id;var sfName=sf.display||sf.name||sf.id;(r.presets||[]).forEach(function(p){all.push({...p,_sfId:sfId,_sfName:sfName,_sfDisplay:sfName.substring(0,30)});});});setSfPresets(all.length>0?all:[]);}catch(e){setSfPresets([]);}}).catch(function(){setSfPresets([]);});};const closeInstrumentSelector=()=>{setInstrumentSelectorTrackId(null);setSynthCategory(null);setSelectedSoundFontId(null);setSfPresetSearchQuery('');};const[synthCategory,setSynthCategory]=useState(null);// 'vst' | 'soundfont'
|
||||||
const[selectedSoundFontId,setSelectedSoundFontId]=useState(null);const[sfPresets,setSfPresets]=useState(null);// presets from SoundFont
|
const[selectedSoundFontId,setSelectedSoundFontId]=useState(null);const[sfPresets,setSfPresets]=useState(null);// presets from SoundFont
|
||||||
const[instrumentDropdownTrackId,setInstrumentDropdownTrackId]=useState(null);const[instrumentDropdownBtnRect,setInstrumentDropdownBtnRect]=useState(null);const[instrumentSearchQuery,setInstrumentSearchQuery]=useState('');const[sfPresetSearchQuery,setSfPresetSearchQuery]=useState('');const filteredInstruments=useMemo(()=>{if(!instrumentSelectorData||!instrumentSearchQuery)return{soundfonts:instrumentSelectorData?.soundfonts||[],vst:instrumentSelectorData?.vst_instruments||[]};const q=instrumentSearchQuery.toLowerCase();return{soundfonts:(instrumentSelectorData.soundfonts||[]).filter(sf=>(sf.display||sf.name||sf.id).toLowerCase().includes(q)),vst:(instrumentSelectorData.vst_instruments||[]).filter(v=>(v.name||v.id).toLowerCase().includes(q))};},[instrumentSearchQuery,instrumentSelectorData]);useEffect(()=>{window.SonicAPI.listPlugins().then(async data=>{try{const catResp=(await window.SonicAPI.getSoundfontCatalog?.())??(await fetch('/api/v1/plugins/soundfonts/catalog').then(r=>r.json()));const catalog=catResp.full_catalog||{};data.soundfonts=(data.soundfonts||[]).map(sf=>{const sfId=sf.id.replace('sf_','');const catEntry=catalog[sfId.toLowerCase()]||catalog[sfId];if(catEntry&&catEntry.instruments){return{...sf,presets:catEntry.instruments};}return sf;});}catch(e){console.warn('Catalog fetch error:',e);}setInstrumentSelectorData(data);}).catch(()=>{});},[instrumentRefreshKey]);useEffect(()=>{if(!instrumentDropdownTrackId)return;const handler=e=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSearchQuery('');};document.addEventListener('click',handler);return()=>document.removeEventListener('click',handler);},[instrumentDropdownTrackId]);const GM_INSTRUMENTS=["Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi","Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer","Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion","Acoustic Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics","Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2","Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani","String Ensemble 1","String Ensemble 2","Synth Strings 1","Synth Strings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit","Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","Synth Brass 1","Synth Brass 2","Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet","Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina","Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)","Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)","FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)","Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai","Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal","Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"];const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;var mt=activeTracksRef.current||tracks;var curTrk=null;for(var ci=0;ci<mt.length;ci++){if(mt[ci].id===trackId){curTrk=mt[ci];break;}}// ── Unload Carla bridge khi chuyển từ VSTi sang instrument KHÔNG phải VST ──
|
const[instrumentDropdownTrackId,setInstrumentDropdownTrackId]=useState(null);const[instrumentDropdownBtnRect,setInstrumentDropdownBtnRect]=useState(null);const[instrumentSearchQuery,setInstrumentSearchQuery]=useState('');const[sfPresetSearchQuery,setSfPresetSearchQuery]=useState('');const filteredInstruments=useMemo(()=>{if(!instrumentSelectorData||!instrumentSearchQuery)return{soundfonts:instrumentSelectorData?.soundfonts||[],vst:instrumentSelectorData?.vst_instruments||[]};const q=instrumentSearchQuery.toLowerCase();return{soundfonts:(instrumentSelectorData.soundfonts||[]).filter(sf=>(sf.display||sf.name||sf.id).toLowerCase().includes(q)),vst:(instrumentSelectorData.vst_instruments||[]).filter(v=>(v.name||v.id).toLowerCase().includes(q))};},[instrumentSearchQuery,instrumentSelectorData]);useEffect(()=>{window.SonicAPI.listPlugins().then(async data=>{try{const catResp=(await window.SonicAPI.getSoundfontCatalog?.())??(await fetch('/api/v1/plugins/soundfonts/catalog').then(r=>r.json()));const catalog=catResp.full_catalog||{};data.soundfonts=(data.soundfonts||[]).map(sf=>{const sfId=sf.id.replace('sf_','');const catEntry=catalog[sfId.toLowerCase()]||catalog[sfId];if(catEntry&&catEntry.instruments){return{...sf,presets:catEntry.instruments};}return sf;});}catch(e){console.warn('Catalog fetch error:',e);}setInstrumentSelectorData(data);}).catch(()=>{});},[instrumentRefreshKey]);useEffect(()=>{if(!instrumentDropdownTrackId)return;const handler=e=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSearchQuery('');};document.addEventListener('click',handler);return()=>document.removeEventListener('click',handler);},[instrumentDropdownTrackId]);const GM_INSTRUMENTS=["Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi","Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer","Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion","Acoustic Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics","Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2","Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani","String Ensemble 1","String Ensemble 2","Synth Strings 1","Synth Strings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit","Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","Synth Brass 1","Synth Brass 2","Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet","Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina","Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)","Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)","FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)","Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai","Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal","Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"];const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;var mt=activeTracksRef.current||tracks;var curTrk=null;for(var ci=0;ci<mt.length;ci++){if(mt[ci].id===trackId){curTrk=mt[ci];break;}}// ── Unload Carla bridge khi chuyển từ VSTi sang instrument KHÔNG phải VST ──
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<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/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608101914" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608102044" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
+36
-4
@@ -22,14 +22,37 @@ def _pick_port():
|
|||||||
# (2 engine cung 8000 -> request roi vao engine ngau nhien -> UI loi).
|
# (2 engine cung 8000 -> request roi vao engine ngau nhien -> UI loi).
|
||||||
# Listen socket khong can SO_REUSEADDR (TIME_WAIT chi ap dung cho
|
# Listen socket khong can SO_REUSEADDR (TIME_WAIT chi ap dung cho
|
||||||
# connection socket, khong phai listen socket).
|
# connection socket, khong phai listen socket).
|
||||||
|
# Bind 0.0.0.0 (GIONG uvicorn) — kiem tra dung port ma server se
|
||||||
|
# dung, truong hop port bi chiem tren interface khac khong sot.
|
||||||
try:
|
try:
|
||||||
s.bind(("127.0.0.1", port))
|
s.bind(("0.0.0.0", port))
|
||||||
return port
|
return port
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
return PORT_RANGE[0]
|
return PORT_RANGE[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _lan_ips():
|
||||||
|
"""IPv4 cua may tren LAN (bo loopback) — client browser o may khac mo app."""
|
||||||
|
ips = []
|
||||||
|
try:
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
s.connect(("8.8.8.8", 80))
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
if ip and not ip.startswith("127.") and ip not in ips:
|
||||||
|
ips.append(ip)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
|
||||||
|
ip = info[4][0]
|
||||||
|
if ip and not ip.startswith("127.") and ip not in ips:
|
||||||
|
ips.append(ip)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return ips
|
||||||
|
|
||||||
|
|
||||||
def _parent_alive(pid):
|
def _parent_alive(pid):
|
||||||
# Windows: os.kill(pid, 0) KHONG kiem tra ton tai — no goi
|
# Windows: os.kill(pid, 0) KHONG kiem tra ton tai — no goi
|
||||||
# TerminateProcess (giai thich: moi sig khac CTRL_C/BREAK deu terminate).
|
# TerminateProcess (giai thich: moi sig khac CTRL_C/BREAK deu terminate).
|
||||||
@@ -100,7 +123,10 @@ def main():
|
|||||||
import uvicorn
|
import uvicorn
|
||||||
import app.main # noqa: F401 — import tuong minh de PyInstaller bundle du package app
|
import app.main # noqa: F401 — import tuong minh de PyInstaller bundle du package app
|
||||||
|
|
||||||
config = uvicorn.Config(app.main.app, host="127.0.0.1", port=port,
|
# Bind 0.0.0.0: client browser o may khac tren LAN mo app qua dia chi IP
|
||||||
|
# (http://<LAN-IP>:port) — lam viec realtime voi may chay standalone.
|
||||||
|
# Cua so Tauri (shell) van mo http://127.0.0.1:port nhu cu.
|
||||||
|
config = uvicorn.Config(app.main.app, host="0.0.0.0", port=port,
|
||||||
log_level="info", access_log=False)
|
log_level="info", access_log=False)
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
|
|
||||||
@@ -117,8 +143,14 @@ def main():
|
|||||||
return
|
return
|
||||||
threading.Thread(target=_watchdog, daemon=True).start()
|
threading.Thread(target=_watchdog, daemon=True).start()
|
||||||
|
|
||||||
logging.getLogger("desktop_engine").info(
|
logger = logging.getLogger("desktop_engine")
|
||||||
"SonicForge engine listening on 127.0.0.1:%d", port)
|
logger.info("SonicForge engine listening on 0.0.0.0:%d", port)
|
||||||
|
lan_ips = _lan_ips()
|
||||||
|
if lan_ips:
|
||||||
|
logger.info("LAN access — mo browser o may khac: %s",
|
||||||
|
" | ".join("http://%s:%d" % (ip, port) for ip in lan_ips))
|
||||||
|
else:
|
||||||
|
logger.warning("Khong phat hien dia chi LAN — client may khac khong truy cap duoc")
|
||||||
server.run()
|
server.run()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user