Fix AI proxy origin in Tauri, SF2 program_select, VSTi GUI reopen + sample rate bridge

- aiGateway.js: use window.API_BASE_URL (engine port) instead of window.location.origin (tauri://localhost asset protocol returned index.html -> JSON parse error); check content-type before parsing response
- NativeInstrumentEngine.cpp: fluid_synth_program_select for SF2 instrument + program change
- Vst3Instrument.cpp: activate all audio/event buses, channel=0 forced, GUI resize/reopen, kEvent using fix
- main.cpp: VST GUI reopen via WM_DESTROY, OleInitialize, steady_clock loop
- lib.rs: BridgeSampleRate state, restart_bridge_with_sample_rate command, borrow fix
- app.jsx/nativeBridgeService.js: isMidiTrack widening, bridge masterbus connect, held MIDI notes, sample-rate restart, openNativeGUI channel
This commit is contained in:
2026-08-13 11:26:39 +07:00
parent 49549aba2e
commit d168328004
8 changed files with 218 additions and 59 deletions
+49 -7
View File
@@ -22,6 +22,7 @@ use shm::{Shm, ShmState};
struct EngineProcess(Mutex<Option<CommandChild>>);
struct BridgeProcess(Mutex<Option<CommandChild>>);
struct BridgeSampleRate(Mutex<u32>);
/// Kill the currently managed bridge child (if any) — a restart MUST never
/// leave the old bridge running, else two daw_vst_bridge.exe race on the same
@@ -129,12 +130,17 @@ fn spawn_bridge(
}
match found {
Some((bridge_exe, label)) => {
let sample_rate = {
let rate_state = app.state::<BridgeSampleRate>();
let g = rate_state.0.lock().unwrap_or_else(|p| p.into_inner());
*g
};
match app
.shell()
.command(&bridge_exe)
.env("SF_SHM_NAME", shm::SHM_NAME)
.env("SF_PARENT_PID", std::process::id().to_string())
.env("SF_SAMPLE_RATE", "44100") // B8: JS resampler handles mismatches (C5)
.env("SF_SAMPLE_RATE", sample_rate.to_string())
.env("SF_BLOCK_SIZE", "256")
.spawn()
{
@@ -214,7 +220,8 @@ pub fn run() {
load_native_instrument,
open_vst_gui,
bridge_status,
transport_control
transport_control,
restart_bridge_with_sample_rate
])
.setup(|app| {
let res_dir = app
@@ -308,6 +315,7 @@ pub fn run() {
if shm_created.is_some() { "created" } else { "unavailable (non-windows or error)" }
);
app.manage(ShmState(Mutex::new(shm_created)));
app.manage(BridgeSampleRate(Mutex::new(48000)));
// Append bridge diagnostics to spawn.log and spawn the sidecar (B3).
let app_handle = app.handle().clone();
@@ -387,7 +395,7 @@ pub fn run() {
}
}
drop(guard);
std::thread::sleep(std::time::Duration::from_millis(5));
std::thread::sleep(std::time::Duration::from_millis(1));
}
});
@@ -509,7 +517,7 @@ fn load_native_instrument(app: AppHandle, path: String, instrument_type: u8, cha
}
#[tauri::command]
fn open_vst_gui(app: AppHandle, plugin_id: String) -> Result<(), String> {
fn open_vst_gui(app: AppHandle, plugin_id: String, channel: u8) -> Result<(), String> {
// B9: bridge tự tạo native Win32 window cho editor VST3 (không qua
// WebView2 — HTML window cũ vẽ ĐÈ lên GUI plugin). push_control(4,0,0,0)
// với hwnd=0 báo bridge tạo window riêng trên ChannelWorker thread.
@@ -518,8 +526,8 @@ fn open_vst_gui(app: AppHandle, plugin_id: String) -> Result<(), String> {
match lock {
Ok(guard) => match guard.as_ref() {
Some(shm) => {
let ok = shm.push_control(4, 0, 0, 0, &plugin_id);
eprintln!("open_vst_gui: push_control type=4 hwnd=0 (bridge-native) ok={}", ok);
let ok = shm.push_control(4, 0, 0, channel as u32, &plugin_id);
eprintln!("open_vst_gui: push_control type=4 hwnd=0 ch={} ok={}", channel, ok);
}
None => eprintln!("open_vst_gui: bridge shm unavailable"),
},
@@ -557,7 +565,11 @@ fn bridge_status(app: AppHandle) -> Result<BridgeStatus, String> {
write_index: shm.write_index(),
block_timestamp: shm.block_timestamp(),
shm_size_bytes: std::mem::size_of::<shm::SharedAudioBufferIPC>(),
sample_rate: 44100,
sample_rate: {
let rate_state = app.state::<BridgeSampleRate>();
let g = rate_state.0.lock().unwrap_or_else(|p| p.into_inner());
*g
},
block_size: shm::AUDIO_BLOCK_SIZE as u32,
},
None => BridgeStatus {
@@ -596,3 +608,33 @@ fn transport_control(app: AppHandle, kind: String, playhead: Option<u32>) -> Res
.then_some(())
.ok_or_else(|| "control queue full".to_string())
}
#[tauri::command]
fn restart_bridge_with_sample_rate(app: AppHandle, sample_rate: u32) -> Result<(), String> {
println!("restart_bridge_with_sample_rate: {}", sample_rate);
{
let rate_state = app.state::<BridgeSampleRate>();
{
let mut g = rate_state.0.lock().unwrap_or_else(|p| p.into_inner());
*g = sample_rate;
}
}
kill_bridge(&app);
let res_dir = app.path().resource_dir().unwrap_or_default();
let exe_dir = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_path_buf()))
.unwrap_or_default();
let log_dir = std::env::var("APPDATA").unwrap_or_else(|_| ".".into());
let spawn_log_path = std::path::Path::new(&log_dir)
.join("SonicForgeDAW")
.join("logs")
.join("spawn.log");
let mut log_line = format!("[tauri] restarting bridge with sample rate {}\n", sample_rate);
let ok = spawn_bridge(&app, &res_dir, &exe_dir, &mut log_line, &spawn_log_path);
if ok {
Ok(())
} else {
Err("Failed to spawn bridge with new sample rate".into())
}
}