FIX: 6 lỗi âm thanh/Carla/temp-save/Ctrl-S/FX Chain Carla bridge

- Soundfont preview: hủy note đang chờ load soundfont (stopNote/stopAll/panic) — hết âm loop không dừng với MIDI Keyboard; preview dùng channel riêng (applyAITrackInstrument) — hết sai instrument
- Carla bridge: endpoint /carla-stop (all-notes-off OSC + terminate process) + stopBridge() khi track chuyển VSTi -> soundfont — hết âm play qua Carla cũ
- MIDI items play qua Carla khi VSTi loaded: scheduleCarlaNote route vào startTrackPlayback + startLocalTrackPlayback + ghost notes
- Tự động lưu temp khi tắt app: beforeunload/pagehide sendBeacon + autosave 30s + ghi storage/temp/autosave.json + khôi phục khi load lại
- Ctrl-S: desktop -> save-to-disk (Documents/SonicForgeDAW/Projects); docker -> Cloud/local
- FX Chain (Mastering + FX Rack): module Carla Bridge (VST FX) — load/openInCarla + stop/unload, pass-through trong graph
This commit is contained in:
2026-08-10 05:34:32 +00:00
parent d20932a68b
commit c08233a163
10 changed files with 622 additions and 45 deletions
+6
View File
@@ -76,6 +76,9 @@ window.API_BASE_URL = window.API_BASE_URL || window.location.origin;
setCarlaPath: (path) => apiRequest('/api/v1/system/carla-path', { method: 'POST', body: JSON.stringify({ carla_path: path }) }),
// Mở native GUI VSTi trong Carla (chỉ khi runtime=desktop + có Carla local)
openInCarla: (pluginName, pluginPath) => apiRequest('/api/v1/plugins/open-in-carla', { method: 'POST', body: JSON.stringify({ plugin_name: pluginName, plugin_path: pluginPath }) }),
// Unload Carla bridge: tắt mọi note đang ngân + terminate tiến trình Carla
// do app spawn (gọi khi track chuyển từ VSTi sang instrument soundfont)
stopCarla: () => apiRequest('/api/v1/plugins/carla-stop', { method: 'POST', body: JSON.stringify({}) }),
// Gửi MIDI note (track ARM → Carla OSC) để preview VSTi realtime
carlaMidi: (payload) => apiRequest('/api/v1/plugins/carla-midi', { method: 'POST', body: JSON.stringify(payload) }),
// Quick-render preview VSTi (âm thật = âm export, cùng code path)
@@ -109,6 +112,9 @@ window.API_BASE_URL = window.API_BASE_URL || window.location.origin;
cleanupBackups: (keep) => apiRequest('/api/v1/projects/cloud/backups/cleanup', { method: 'POST', body: JSON.stringify({ keep }) }),
renderProject: (projectJson, outputFilename) => apiRequest('/api/v1/plugins/render', { method: 'POST', body: JSON.stringify({ project_json: projectJson, output_filename: outputFilename }) }),
// Ctrl-S desktop: lưu project ra THƯ MỤC CỦA HỆ ĐIỀU HÀNH
// (Documents/SonicForgeDAW/Projects — Windows; ~/SonicForgeDAW/Projects — Linux)
saveProjectToDisk: (name, dataJson) => apiRequest('/api/v1/projects/save-to-disk', { method: 'POST', body: JSON.stringify({ name, data_json: dataJson }) }),
deleteSoundFont: (sfId) => apiRequest(`/api/v1/plugins/soundfont/${sfId}`, { method: 'DELETE' }),
uploadSoundFont: async (file) => {
const formData = new FormData();
+20
View File
@@ -98,5 +98,25 @@ window.SonicCarlaMidi = window.SonicCarlaMidi || {
this.noteOn(channel, note, velocity);
var self = this;
setTimeout(function () { self.noteOff(channel, note); }, (durMs || 300) + 50);
},
// Tắt mọi note đang ngân trong Carla (note_off toàn pitch — dùng khi stop)
allNotesOff: function () {
if (!window.SonicAPI || !window.SonicAPI.carlaMidi) return;
for (var ch = 0; ch < 16; ch++) {
for (var n = 0; n < 128; n++) {
window.SonicAPI.carlaMidi({ event: 'note_off', note: n, channel: ch }).catch(function () {});
}
}
},
// Unload Carla bridge: tắt âm + terminate tiến trình Carla (app spawn).
// Gọi khi track chuyển từ VSTi → instrument soundfont để âm KHÔNG còn
// play qua Carla bridge nữa.
stopBridge: function () {
var self = this;
try { self.allNotesOff(); } catch (e) {}
if (window.SonicAPI && window.SonicAPI.stopCarla) {
return window.SonicAPI.stopCarla().catch(function () {});
}
return Promise.resolve();
}
};
+35 -1
View File
@@ -29,6 +29,13 @@
let _scheduledNotes = [];
let _loadPromises = {};
let _sfloadSeq = 0;
// Note đang CHỜ load soundfont (chưa noteon) — 'ch:pitch' → số lần chờ.
// Đăng ký TRƯỚC khi load để stopNote/stopAll/panic hủy được note này;
// trước đây note deferred bắn TRỄ sau khi thả phím / sau Stop → âm loop
// không dừng (keybed preview âm soundfont).
let _pendingNoteOns = {};
// Tăng mỗi lần stopAll/panic — deferred doNote của generation cũ thành no-op.
let _noteGeneration = 0;
const getCtx = function () {
if (_audioCtx) {
@@ -468,8 +475,11 @@
stopNote: function (channel, pitch) {
if (channel < 0 || channel > 15) return;
var key = channel + ':' + pitch;
// Hủy note đang CHỜ LOAD soundfont (chưa noteon) — trước đây note
// này vẫn bắn TRỄ sau khi thả phím → âm loop không dừng được.
if (_pendingNoteOns[key]) delete _pendingNoteOns[key];
if (_initialized && _fluidModule) {
var key = channel + ':' + pitch;
var mappedChs = _activeNotes[key];
if (mappedChs === undefined) mappedChs = [channel];
for (var i = 0; i < mappedChs.length; i++) {
@@ -564,7 +574,24 @@
return;
}
console.log('[SonicSF] soundfont not loaded yet, loading:', finalSfId);
// Đăng ký note CHỜ LOAD trước khi load — để stopNote (thả
// phím) / stopAll / panic hủy được note này. Trước đây
// note deferred vẫn bắn TRỄ sau khi thả phím hoặc sau
// Stop → âm loop không dừng với preview MIDI Keyboard.
var _pendKey = ch + ':' + midiPitch;
var _gen = _noteGeneration;
_pendingNoteOns[_pendKey] = (_pendingNoteOns[_pendKey] || 0) + 1;
self.loadSoundFont(finalSfId).then(function (ok) {
// stopAll/panic chạy trong lúc load → generation đổi → bỏ
if (_gen !== _noteGeneration) return;
// stopNote (thả phím) đã hủy → KHÔNG bắn note trễ nữa
var _pend = _pendingNoteOns[_pendKey];
if (_pend) {
_pendingNoteOns[_pendKey] = _pend - 1;
if (_pendingNoteOns[_pendKey] <= 0) delete _pendingNoteOns[_pendKey];
} else {
return;
}
console.log('[SonicSF] loadSoundFont result:', ok, 'for:', finalSfId);
if (ok) {
doNote();
@@ -666,6 +693,9 @@
panic: function () {
_scheduledNotes.forEach(function (sn) { if (sn.on) { clearTimeout(sn.on); sn.on = null; } });
_scheduledNotes = [];
// Hủy mọi note đang CHỜ LOAD soundfont (deferred) — note cũ thành no-op
_noteGeneration++;
_pendingNoteOns = {};
if (_initialized && _fluidModule) {
// noteoff TỪNG note đang ngân (binding _fluid_synth_noteoff chắc
// chắn tồn tại — đã dùng cho duration hết) — all_notes_off có
@@ -743,6 +773,10 @@
},
stopAll: function () {
// Hủy mọi note đang CHỜ LOAD soundfont (deferred) — note cũ thành
// no-op sau Stop (âm loop không dừng khi preview MIDI Keyboard).
_noteGeneration++;
_pendingNoteOns = {};
if (_initialized && _fluidModule) {
// noteoff từng note đang ngân (binding chắc chắn tồn tại) —
// phòng all_notes_off không có trong WASM exports.