// SonicForge Runtime service — phát hiện môi trường chạy (desktop Windows / // docker headless) qua /api/v1/system/capabilities, bật/tắt tính năng theo đó. // - data-runtime trên : "desktop" | "headless" // - data-carla="1": có Carla local (hiện nút "Mở trong Carla") // - Phần tử có thuộc tính data-carla-only sẽ bị ẩn khi không có Carla local. // - Thư viện preset (.vstpreset) cache trong SonicRuntime.presets — dùng cho // dropdown gán preset vào track (Carla → pedalboard bridge). window.SonicRuntime = window.SonicRuntime || { loaded: false, capabilities: null, presets: null }; (function () { function getHeaders() { const token = localStorage.getItem('sonic_token') || ''; return token ? { 'Authorization': 'Bearer ' + token } : {}; } function load() { return fetch(window.API_BASE_URL + '/api/v1/system/capabilities') .then(function (r) { return r.json(); }) .then(function (data) { var c = data && data.success ? data : { features: {} }; window.SonicRuntime.capabilities = c; window.SonicRuntime.loaded = true; var html = document.documentElement; html.dataset.runtime = c.runtime || 'unknown'; html.dataset.platform = c.platform || ''; html.dataset.carla = (c.features && c.features.carla_local) ? '1' : '0'; if (c.features && c.features.carla_local === false) { document.querySelectorAll('[data-carla-only]').forEach(function (el) { el.style.display = 'none'; }); } // Cache sẵn danh sách preset (static, ít thay đổi) listPresets().catch(function () {}); return c; }) .catch(function () { return null; }); } function listPresets() { if (window.SonicRuntime.presets) return Promise.resolve(window.SonicRuntime.presets); return fetch(window.API_BASE_URL + '/api/v1/presets', { headers: getHeaders() }) .then(function (r) { return r.json(); }) .then(function (d) { window.SonicRuntime.presets = (d && d.presets) || []; return window.SonicRuntime.presets; }) .catch(function () { return []; }); } window.SonicRuntime.load = load; window.SonicRuntime.listPresets = listPresets; window.SonicRuntime.refreshPresets = function () { window.SonicRuntime.presets = null; return window.SonicRuntime.listPresets(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', load); } else { load(); } })(); // ── SonicCarlaMidi: cầu nối MIDI từ track ARM → Carla (OSC /Carla/0/note_*) ── // Khi track dùng VSTi (synth_engine.type chứa 'vst') + được ARM + máy có Carla // local → phím bấm trên piano roll / keybed được gửi tới Carla để phát realtime // (Carla standalone bật OSC UDP mặc định cổng 22752). window.SonicCarlaMidi = window.SonicCarlaMidi || { shouldRoute: function (synthEngine, isArmed) { try { var c = window.SonicRuntime && window.SonicRuntime.capabilities; if (!c || !c.features || !c.features.carla_local) return false; if (!isArmed) return false; var se = synthEngine || {}; return String(se.type || '').indexOf('vst') !== -1 && !!se.plugin_id; } catch (e) { return false; } }, // Playback MIDI items: route khi track VSTi + Carla local (không cần ARM — // user đã chủ động bấm Play trên item đó). shouldRoutePlayback: function (synthEngine) { try { var c = window.SonicRuntime && window.SonicRuntime.capabilities; if (!c || !c.features || !c.features.carla_local) return false; var se = synthEngine || {}; return String(se.type || '').indexOf('vst') !== -1 && !!se.plugin_id; } catch (e) { return false; } }, noteOn: function (channel, note, velocity) { if (!window.SonicAPI || !window.SonicAPI.carlaMidi) return; window.SonicAPI.carlaMidi({ event: 'note_on', note: note, velocity: velocity || 100, channel: channel || 0 }).catch(function () {}); }, noteOff: function (channel, note) { if (!window.SonicAPI || !window.SonicAPI.carlaMidi) return; window.SonicAPI.carlaMidi({ event: 'note_off', note: note, channel: channel || 0 }).catch(function () {}); }, // Bật note rồi tự tắt sau durMs (preview ngắn) playNote: function (channel, note, velocity, durMs) { 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(); } };