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:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user