fix(midi): playback khong cam loop pass 2+ va note cung pitch khong bi cat (bugs 1-2) - set isPlayingRef truoc bridge PLAY (startTrackPlayback/local/piano-roll), cancel STOP#2 retry khi loop restart; dedupe NOTE_OFF theo channel:pitch (pendingNoteOffMap + scheduleNoteOff) de NOTE_OFF tre (dur+30ms) khong cat note-on cung pitch ke tiep (bar lien nhau, piano roll note chong), clear map khi stop; sync 5 ban JS + index.html ?v=202608181807, rebuild exe release

This commit is contained in:
2026-08-18 18:13:36 +07:00
parent 6b6328044c
commit 45a15da55e
5 changed files with 2157 additions and 2021 deletions
+40 -1
View File
@@ -754,6 +754,41 @@ struct BridgeStatus {
shm_size_bytes: usize,
sample_rate: u32,
block_size: u32,
// V10: channel -> path mà C++ đã load/restore (bridge_state.json). JS dùng
// làm nguồn sự thật khi webview không load được qua HTTP (token hết hạn /
// listPlugins thiếu path) — C++ restore độc lập nên GUI vẫn mở được.
loaded_channels: Vec<BridgeLoadedChannel>,
}
#[derive(serde::Serialize)]
struct BridgeLoadedChannel {
channel: u32,
path: String,
}
#[derive(serde::Deserialize)]
struct BridgeStateFile {
#[serde(default)]
instruments: Vec<BridgeStateInstrument>,
}
#[derive(serde::Deserialize)]
struct BridgeStateInstrument {
channel: u32,
path: String,
}
/// %APPDATA%/SonicForgeDAW/state/bridge_state.json — C++ StateStore::save ghi
/// mỗi ~2s khi có instrument + khi DUMP_STATE lúc pre-kill (main.cpp G2.1).
fn read_bridge_loaded_channels() -> Vec<BridgeLoadedChannel> {
let Some(appdata) = std::env::var("APPDATA").ok() else { return Vec::new() };
let path = Path::new(&appdata).join("SonicForgeDAW").join("state").join("bridge_state.json");
let Ok(raw) = std::fs::read_to_string(&path) else { return Vec::new() };
let Ok(state): Result<BridgeStateFile, _> = serde_json::from_str(&raw) else { return Vec::new() };
state.instruments
.iter()
.map(|i| BridgeLoadedChannel { channel: i.channel, path: i.path.clone() })
.collect()
}
/// %APPDATA%/SonicForgeDAW/ipc — cùng dir engine (plugins.py) đọc cho
@@ -767,7 +802,7 @@ fn bridge_ipc_dir() -> Option<PathBuf> {
fn bridge_status(app: AppHandle) -> Result<BridgeStatus, String> {
let state = app.state::<ShmState>();
let guard = state.0.lock().map_err(|e| e.to_string())?;
let status = match guard.as_ref() {
let mut status = match guard.as_ref() {
Some(shm) => BridgeStatus {
connected: true,
shm_name: shm::shm_name(),
@@ -780,6 +815,7 @@ fn bridge_status(app: AppHandle) -> Result<BridgeStatus, String> {
*g
},
block_size: shm::AUDIO_BLOCK_SIZE as u32,
loaded_channels: Vec::new(),
},
None => BridgeStatus {
connected: false,
@@ -789,8 +825,11 @@ fn bridge_status(app: AppHandle) -> Result<BridgeStatus, String> {
shm_size_bytes: 0,
sample_rate: 0,
block_size: 0,
loaded_channels: Vec::new(),
},
};
// V10: C++ loaded channels (state file) — không phụ thuộc HTTP/auth.
status.loaded_channels = read_bridge_loaded_channels();
// E1: ghi JSON → engine /api/v1/bridge/status (badge Plugin Manager phải
// phản ánh đúng bridge thật — trước đây file không tồn tại → luôn OFF).
if let Some(dir) = bridge_ipc_dir() {