fix(midi): route MIDI live via masterBus, fix media explorer drag, add action logging
- TrackInstrument: gate native live path (SF/VSTi WASAPI bridge bypassed masterBus -> mastering FX + main out VU dead). Fallback to SonicSF WASM through masterBus.input; native kept for offline render only. - MEDIA_LIBRARY_SAMPLES: rename MIDI_Loop_02_Bass/03_Lead/05_Bass to match actual /static/midi files (drag was 404 -> parse error). - Add SonicAppLogger (console + POST /api/v1/logs) wired to: instrument load error, VSTi autosample fail, MIDI item -> mastering fx chain, play -> main out, drag MIDI errors (media explorer + window explorer). - Add backend /api/v1/logs endpoint (server-side log, no DB).
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# SonicForge Client Logs API — client gửi log hành động (MIDI/mastering/drag
|
||||||
|
# errors) qua POST /api/v1/logs. Không lưu DB — in ra server log (uvicorn
|
||||||
|
# console). Public: log không chứa dữ liệu nhạy (client tự quyết định).
|
||||||
|
import logging
|
||||||
|
from typing import Any, Optional
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
_logger = logging.getLogger("sonicforge.client_logs")
|
||||||
|
|
||||||
|
|
||||||
|
class ClientLogEntry(BaseModel):
|
||||||
|
category: str = "GENERAL"
|
||||||
|
level: str = "info"
|
||||||
|
message: str = ""
|
||||||
|
data: Optional[dict[str, Any]] = None
|
||||||
|
url: Optional[str] = None
|
||||||
|
ts: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("")
|
||||||
|
async def post_client_log(entry: ClientLogEntry):
|
||||||
|
"""Ghi log từ client vào server log (fire-and-forget, luôn 200)."""
|
||||||
|
tag = f"[ClientLog][{entry.category}][{entry.level}]"
|
||||||
|
line = f"{tag} {entry.message}"
|
||||||
|
if entry.data:
|
||||||
|
line += f" {entry.data}"
|
||||||
|
if entry.ts:
|
||||||
|
line += f" (ts={entry.ts})"
|
||||||
|
if entry.url:
|
||||||
|
line += f" url={entry.url}"
|
||||||
|
if entry.level == "error":
|
||||||
|
_logger.error(line)
|
||||||
|
elif entry.level == "warn":
|
||||||
|
_logger.warning(line)
|
||||||
|
else:
|
||||||
|
_logger.info(line)
|
||||||
|
return {"ok": True}
|
||||||
@@ -21,6 +21,7 @@ from app.api.v1.media import router as media_router
|
|||||||
from app.api.v1.system import router as system_router
|
from app.api.v1.system import router as system_router
|
||||||
from app.api.v1.presets import router as presets_router
|
from app.api.v1.presets import router as presets_router
|
||||||
from app.api.v1.native import router as native_router
|
from app.api.v1.native import router as native_router
|
||||||
|
from app.api.v1.logs import router as logs_router
|
||||||
from app.core.auth import seed_admin
|
from app.core.auth import seed_admin
|
||||||
from app.core.soundfont_scanner import SoundFontAutoScanner
|
from app.core.soundfont_scanner import SoundFontAutoScanner
|
||||||
|
|
||||||
@@ -114,6 +115,7 @@ app.include_router(media_router, prefix="/api/v1/media", tags=["media"])
|
|||||||
app.include_router(system_router, prefix="/api/v1/system", tags=["system"])
|
app.include_router(system_router, prefix="/api/v1/system", tags=["system"])
|
||||||
app.include_router(presets_router, prefix="/api/v1/presets", tags=["presets"])
|
app.include_router(presets_router, prefix="/api/v1/presets", tags=["presets"])
|
||||||
app.include_router(native_router, prefix="/api/v1/native", tags=["native"])
|
app.include_router(native_router, prefix="/api/v1/native", tags=["native"])
|
||||||
|
app.include_router(logs_router, prefix="/api/v1/logs", tags=["logs"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
+21
-5
@@ -6284,7 +6284,9 @@ const preloadTrackInstruments = async (tracks) => {
|
|||||||
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
|
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
|
||||||
try {
|
try {
|
||||||
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
|
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('INSTRUMENT', 'Load instrument fail', { track: t.name, sfId: sfId, bank: bank, prog: prog, err: e && e.message }); } catch (e2) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13203,10 +13205,10 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin
|
|||||||
// ── Media Explorer Panel (from md/51_MEDIA_EXPLORER.md) ──
|
// ── Media Explorer Panel (from md/51_MEDIA_EXPLORER.md) ──
|
||||||
const MEDIA_LIBRARY_SAMPLES = [
|
const MEDIA_LIBRARY_SAMPLES = [
|
||||||
{ name: "MIDI_Loop_01.mid", events: 95, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
{ name: "MIDI_Loop_01.mid", events: 95, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
||||||
{ name: "MIDI_Loop_02_Bass.mid", events: 48, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
{ name: "MIDI_Loop_02.mid", events: 48, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
||||||
{ name: "MIDI_Loop_03_Lead.mid", events: 110, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
{ name: "MIDI_Loop_03.mid", events: 110, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
||||||
{ name: "MIDI_Loop_04.mid", events: 76, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
{ name: "MIDI_Loop_04.mid", events: 76, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
||||||
{ name: "MIDI_Loop_05_Bass.mid", events: 52, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
{ name: "MIDI_Loop_05.mid", events: 52, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" },
|
||||||
{ name: "MIDI_Loop_06.mid", events: 88, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" }
|
{ name: "MIDI_Loop_06.mid", events: 88, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130, kind: "midi" }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -15592,7 +15594,10 @@ const App = () => {
|
|||||||
window.SonicVstiAutosample.ensure(_warmEngine).then(_sfId => {
|
window.SonicVstiAutosample.ensure(_warmEngine).then(_sfId => {
|
||||||
if (!_sfId && window.SonicVstiAutosample.failReasonFor) {
|
if (!_sfId && window.SonicVstiAutosample.failReasonFor) {
|
||||||
const _reason = window.SonicVstiAutosample.failReasonFor(_warmEngine);
|
const _reason = window.SonicVstiAutosample.failReasonFor(_warmEngine);
|
||||||
if (_reason && window.showToast) window.showToast('Autosample VSTi that bai: ' + _reason + ' - dung am default', 'warning');
|
if (_reason) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('VSTI', 'Autosample VSTi fail', { plugin_id: instrumentId, reason: _reason }); } catch (e2) {}
|
||||||
|
if (window.showToast) window.showToast('Autosample VSTi that bai: ' + _reason + ' - dung am default', 'warning');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -21879,6 +21884,11 @@ const App = () => {
|
|||||||
const routeCarla = shouldRouteCarla(track.synth_engine);
|
const routeCarla = shouldRouteCarla(track.synth_engine);
|
||||||
const nativeSf = isStandaloneSf() && !routeCarla && isSfTrackEngine(track.synth_engine);
|
const nativeSf = isStandaloneSf() && !routeCarla && isSfTrackEngine(track.synth_engine);
|
||||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf)) {
|
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf)) {
|
||||||
|
try {
|
||||||
|
const _mActive = !!(masterBus && masterBus.masteringActive);
|
||||||
|
window.SonicAppLogger && window.SonicAppLogger.info('MIDI_ITEM', nativeSf ? 'render_offline_native -> WAV -> mastering fx chain' : 'WASM -> masterBus.input -> mastering fx chain', { track: track.id, name: track.name, items: midiItems.length, masteringActive: _mActive, nativeSf: !!nativeSf });
|
||||||
|
window.SonicAppLogger && window.SonicAppLogger.info('MIDI_ITEM', 'play_qua_main_out', { track: track.id, name: track.name, items: midiItems.length, dest: 'masterBus.analyser -> ctx.destination' });
|
||||||
|
} catch (e2) {}
|
||||||
if (nativeSf) {
|
if (nativeSf) {
|
||||||
midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||||
} else {
|
} else {
|
||||||
@@ -24540,9 +24550,11 @@ const App = () => {
|
|||||||
return new File([blob], mef.name, { type: 'audio/midi' });
|
return new File([blob], mef.name, { type: 'audio/midi' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Media Explorer drag resolve fail', { name: mef && mef.name, kind: mef && mef.kind, err: e && e.message }); } catch (e2) {}
|
||||||
showToast('Không thể nạp file từ Media Explorer: ' + e.message, 'error');
|
showToast('Không thể nạp file từ Media Explorer: ' + e.message, 'error');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Media Explorer drag missing file data', { name: mef && mef.name, kind: mef && mef.kind }); } catch (e2) {}
|
||||||
showToast('Không thể nạp file từ Media Explorer: thiếu dữ liệu file.', 'error');
|
showToast('Không thể nạp file từ Media Explorer: thiếu dữ liệu file.', 'error');
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -24557,6 +24569,7 @@ const App = () => {
|
|||||||
var arrayBuffer = await file.arrayBuffer();
|
var arrayBuffer = await file.arrayBuffer();
|
||||||
var midiResult = parseMidiFile(arrayBuffer);
|
var midiResult = parseMidiFile(arrayBuffer);
|
||||||
if (!midiResult || midiResult.length === 0) {
|
if (!midiResult || midiResult.length === 0) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Window explorer drag parse empty', { name: fileName, size: file.size }); } catch (e2) {}
|
||||||
showToast('Không tìm thấy nốt nhạc trong file MIDI.', 'error');
|
showToast('Không tìm thấy nốt nhạc trong file MIDI.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -24612,6 +24625,7 @@ const App = () => {
|
|||||||
} : t));
|
} : t));
|
||||||
showToast(`Nạp file thành công: ${fileName} (${channelInfo.label})`, 'success');
|
showToast(`Nạp file thành công: ${fileName} (${channelInfo.label})`, 'success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Window explorer drag load fail', { name: fileName, isMidi: isMidi, err: err && err.message }); } catch (e2) {}
|
||||||
showToast(isMidi ? "Lỗi giải mã MIDI." : "Lỗi giải mã âm thanh. Định dạng file không tương thích.", 'error');
|
showToast(isMidi ? "Lỗi giải mã MIDI." : "Lỗi giải mã âm thanh. Định dạng file không tương thích.", 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -24624,6 +24638,7 @@ const App = () => {
|
|||||||
var arrayBuffer = await file.arrayBuffer();
|
var arrayBuffer = await file.arrayBuffer();
|
||||||
var midiResult = parseMidiFile(arrayBuffer);
|
var midiResult = parseMidiFile(arrayBuffer);
|
||||||
if (!midiResult || midiResult.length === 0) {
|
if (!midiResult || midiResult.length === 0) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Drag MIDI parse empty', { name: file && file.name, size: file && file.size }); } catch (e2) {}
|
||||||
showToast('Không tìm thấy nốt nhạc trong file MIDI.', 'error');
|
showToast('Không tìm thấy nốt nhạc trong file MIDI.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -24645,6 +24660,7 @@ const App = () => {
|
|||||||
if (newTracks.length > 0) setSelectedTrackId(newTracks[0].id);
|
if (newTracks.length > 0) setSelectedTrackId(newTracks[0].id);
|
||||||
showToast('Đã tải MIDI: ' + newTracks.length + ' track(s) từ ' + file.name, 'success');
|
showToast('Đã tải MIDI: ' + newTracks.length + ' track(s) từ ' + file.name, 'success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
try { window.SonicAppLogger && window.SonicAppLogger.error('DRAG_MIDI', 'Drag MIDI decode fail', { name: file && file.name, err: err && err.message }); } catch (e2) {}
|
||||||
showToast('Lỗi giải mã MIDI.', 'error');
|
showToast('Lỗi giải mã MIDI.', 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,40 @@
|
|||||||
|
// SonicForge App Logger — ghi hành động quan trọng của client (console + POST
|
||||||
|
// /api/v1/logs để server log). Fire-and-forget: không throw, không block UI.
|
||||||
|
// Các category dùng chung:
|
||||||
|
// INSTRUMENT — lỗi load instrument (soundfont select/load fail)
|
||||||
|
// VSTI — VSTi load/autosample/preview fail
|
||||||
|
// MIDI_ITEM — MIDI item qua mastering fx chain / main out (playback)
|
||||||
|
// DRAG_MIDI — lỗi drag MIDI từ MEDIA EXPLORER hoặc window explorer
|
||||||
|
window.SonicAppLogger = window.SonicAppLogger || (function () {
|
||||||
|
function base() {
|
||||||
|
return window.API_BASE_URL || window.location.origin || '';
|
||||||
|
}
|
||||||
|
function emit(category, level, message, data) {
|
||||||
|
try {
|
||||||
|
var line = '[AppLog][' + category + '][' + level + '] ' + message;
|
||||||
|
if (data !== undefined && data !== null) {
|
||||||
|
try { line += ' ' + JSON.stringify(data); } catch (e) {}
|
||||||
|
}
|
||||||
|
if (level === 'error') console.error(line); else if (level === 'warn') console.warn(line); else console.log(line);
|
||||||
|
try {
|
||||||
|
fetch(base() + '/api/v1/logs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
category: category,
|
||||||
|
level: level,
|
||||||
|
message: message,
|
||||||
|
data: data !== undefined ? data : null,
|
||||||
|
url: window.location ? window.location.href : '',
|
||||||
|
ts: new Date().toISOString()
|
||||||
|
})
|
||||||
|
}).catch(function () {});
|
||||||
|
} catch (e) {}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
info: function (category, message, data) { emit(category, 'info', message, data); },
|
||||||
|
warn: function (category, message, data) { emit(category, 'warn', message, data); },
|
||||||
|
error: function (category, message, data) { emit(category, 'error', message, data); }
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -22,7 +22,15 @@ window.TrackInstrument = window.TrackInstrument || {};
|
|||||||
|
|
||||||
// Loại engine native cho track: 'sf' (soundfont), 'vst3'/'vst2' (VSTi có
|
// Loại engine native cho track: 'sf' (soundfont), 'vst3'/'vst2' (VSTi có
|
||||||
// plugin_id) hay null (không native được → fallback WASM/autosample).
|
// plugin_id) hay null (không native được → fallback WASM/autosample).
|
||||||
|
// T15-regression fix: native live (SF/VSTi qua WASAPI bridge) phát thẳng ra
|
||||||
|
// OS device — BỎ QUA WebAudio masterBus → mastering FX + Main out VU không
|
||||||
|
// nhận tín hiệu. Gate native live path: luôn fallback WASM (SonicSF/
|
||||||
|
// autosample) qua masterBus.input. Native vẫn dùng cho OFFLINE render
|
||||||
|
// (scheduleNativeSfItem / /api/v1/native/render) — không đi qua hàm này.
|
||||||
|
// ponytail: re-enable live native khi C++ bridge có capture/readback →
|
||||||
|
// set window.__SONICFORGE_NATIVE_LIVE = true.
|
||||||
TrackInstrument.prototype._nativeKind = function () {
|
TrackInstrument.prototype._nativeKind = function () {
|
||||||
|
if (!window.__SONICFORGE_NATIVE_LIVE) return null;
|
||||||
try {
|
try {
|
||||||
if (this.sfId) return 'sf';
|
if (this.sfId) return 'sf';
|
||||||
if (window.SonicNativeAudio && this.synthEngine && this.synthEngine.plugin_id) {
|
if (window.SonicNativeAudio && this.synthEngine && this.synthEngine.plugin_id) {
|
||||||
|
|||||||
@@ -34,11 +34,12 @@
|
|||||||
<script src="/static/vendor/react-dom.production.min.js"></script>
|
<script src="/static/vendor/react-dom.production.min.js"></script>
|
||||||
<script src="/static/js/services/fluidsynthLoader.js?v=202607271245"></script>
|
<script src="/static/js/services/fluidsynthLoader.js?v=202607271245"></script>
|
||||||
<script src="/static/js/services/runtime.js?v=202608111230"></script>
|
<script src="/static/js/services/runtime.js?v=202608111230"></script>
|
||||||
<script src="/static/js/services/api.js?v=202608111230"></script>
|
<script src="/static/js/services/api.js?v=202608111301"></script>
|
||||||
|
<script src="/static/js/services/appLogger.js?v=202608111301"></script>
|
||||||
<script src="/static/js/services/vstiAutosample.js?v=202608111230"></script>
|
<script src="/static/js/services/vstiAutosample.js?v=202608111230"></script>
|
||||||
<script src="/static/js/services/unifiedMidiRouter.js?v=202608111230"></script>
|
<script src="/static/js/services/unifiedMidiRouter.js?v=202608111230"></script>
|
||||||
<script src="/static/js/services/nativeAudioClient.js?v=202608111230"></script>
|
<script src="/static/js/services/nativeAudioClient.js?v=202608111230"></script>
|
||||||
<script src="/static/js/services/trackInstrument.js?v=202608111230"></script>
|
<script src="/static/js/services/trackInstrument.js?v=202608111301"></script>
|
||||||
<script src="/static/js/services/audioEngine.js?v=202607271016"></script>
|
<script src="/static/js/services/audioEngine.js?v=202607271016"></script>
|
||||||
<script src="/static/js/services/storage.js?v=202608038200"></script>
|
<script src="/static/js/services/storage.js?v=202608038200"></script>
|
||||||
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
|
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608111230" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608111301" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
Reference in New Issue
Block a user