33 Commits

Author SHA1 Message Date
3dtours 8e3c652551 IMPROVE: PIANO ROLL TAB khi play thì playhead luôn nằm chính giữa piano roll 2026-08-07 22:10:37 +07:00
3dtours 393df8fd7b FIX: cho phép shift+click để chọn notes, drag để di chuyển các notes, ctrl+drag để copy nhanh các notes 2026-08-07 21:48:14 +07:00
3dtours 688a7a57e2 FIX: cho phép copy/cut các notes được chọn trong PIANO ROLL TAB 2026-08-07 21:39:18 +07:00
3dtours 652e745d0a IMPROVE: thay đổi giao diện của PIANO ROLL TAB 2026-08-07 21:22:13 +07:00
3dtours 45ab31d42e IMPROVE: thêm các tính năng cho MIDI trong PIANO ROLL TAB 2026-08-07 20:46:47 +07:00
3dtours c3182f6baa FIX: sửa lỗi không hiển thị đúng nội dung của SECTION-TAB trong section item 2026-08-07 18:58:29 +07:00
3dtours 1d9b426d15 FIX: sửa lỗi copy/cut paste không đúng item 2026-08-07 17:06:05 +07:00
3dtours 4f34491f3e FIX: sửa lỗi hiển thị animation của VU meter khi play MIDI 2026-08-07 15:57:49 +07:00
3dtours 9e269d3b79 FIX: sửa lỗi hiển thị animation của VU meter 2026-08-07 15:40:15 +07:00
3dtours 58c6ec794d FIX: sửa lỗi hiển thị số bar bị sai 2026-08-07 15:07:06 +07:00
3dtours 173a1f227b FIX: sửa lỗi lưu thay đổi trong SECTION-TAB thì cũng tạo duration trên MAIN SESSION 2026-08-07 11:08:39 +07:00
3dtours c1aa1eb4d4 FIX: sửa lỗi phát hiện đã không còn play midi note để PIANO ROLL TAB quay về vị trí 0 2026-08-07 10:41:45 +07:00
3dtours 6c16bf8410 FIX: sửa lỗi đồng bộ playhead vị trí play giữa MAIN SESSION/SECTION-TAB/PIANO ROLL TAB 2026-08-07 10:37:34 +07:00
3dtours 4981785913 FIX: sửa lỗi playhead đồng bộ giữa các tab, nhấn space có thể bind với phím play 2026-08-07 10:27:21 +07:00
3dtours bca661bffd FIX: sửa lỗi chuyển từ tab PIANO ROLL TAB sang MAIN SESSION/SECTION-TAB không đồng bộ playhead 2026-08-07 10:12:18 +07:00
3dtours 4c820bfa77 FIX: sửa lỗi chuyển từ tab PIANO ROLL TAB sang MAIN SESSION và SECTION-TAB thì bị câm 2026-08-07 09:35:40 +07:00
3dtours 8cdf2942b9 FIX: sửa lỗi chuyển từ tab MAIN SESSION sang SECTION-TAB và PIANO ROLL TAB thì bị bypass 2026-08-07 09:13:03 +07:00
3dtours 23675dab83 FIX: Đã fix preview Media Explorer (v202608062345) 2026-08-06 20:45:56 +07:00
3dtours 71b9231b57 FIX: Đã fix block lồng + mất MIDI (v202608062330) 2026-08-06 20:42:27 +07:00
3dtours 93dc13b17a FIX: lỗi bypass của SECTION-TAB 2026-08-06 18:19:04 +07:00
3dtours f1e914fa62 FIX: lỗi: CC7 (channel volume) chỉ được set 100/0 (mute/unmute) — volume track KHÔNG áp qua channel → track MIDI dùng chung channel: CC7 của track set cuối thắng → volume của track kia vô tác dụng 2026-08-06 14:10:57 +07:00
3dtours 4dfab954d8 FIX: reset tất cả về mặc định khi tạo new project 2026-08-06 11:47:10 +07:00
3dtours e99e54773f FIX: Đã sắp xếp lại (v202608061030) 2026-08-06 08:29:23 +07:00
3dtours 0ba31c57bf FIX: Đã đổi tên (v202608060900) 2026-08-05 22:23:31 +07:00
3dtours 454dd91f96 FIX: Đã fix cross-machine (v202608060700) 2026-08-05 22:02:43 +07:00
3dtours e9f29e09ca FIX: Đã fix cross-machine (v202608060500) 2026-08-05 17:40:35 +07:00
3dtours 289746f187 FIX: Đã fix (v202608060430) — tab deactive → stop âm tab đó 2026-08-05 17:29:52 +07:00
3dtours b3ced7a7b3 FIX: Clone AI Var kế thừa midiChannel của track gốc → 2 track DÙNG CHUNG channel 2026-08-05 15:49:44 +07:00
3dtours 4233c1eeda FIX: Piano roll track 4 phát âm thanh instrument track 3 2026-08-05 15:17:13 +07:00
3dtours 35f7c3822f FIX: Piano roll tab không xuất âm thanh qua mastering chain được recovery 2026-08-05 15:05:44 +07:00
3dtours d37f4e7557 FIX: Piano roll tab không xuất âm thanh qua mastering chain được recovery 2026-08-05 12:30:42 +07:00
3dtours 8a9d6b3c27 FIX: Piano roll tab không xuất âm thanh qua mastering chain được recovery nhưng âm thanh rất nhỏ 2026-08-05 12:16:00 +07:00
3dtours 55d3464b1e FIX: Piano roll tab không xuất âm thanh qua mastering chain 2026-08-05 12:10:50 +07:00
8 changed files with 4082 additions and 497 deletions
+29
View File
@@ -259,3 +259,32 @@ async def get_profile(current_user: dict = Depends(get_current_user)):
"max_tracks": row["max_tracks"] or 16
}
}
@router.get("/first-time")
async def auth_first_time():
# "Lần đăng nhập đầu" = tài khoản admin vẫn dùng mật khẩu MẶC ĐỊNH
# (chưa từng đổi). Sau khi đổi lần đầu → first_time = false → UI xóa
# gợi ý username/mật khẩu (Tùy chọn - Admin có thể bỏ trống, lần đầu:
# admin123, nút Điền nhanh).
try:
conn = get_db_connection()
try:
cur = conn.cursor()
cur.execute(
"SELECT id, hashed_password, must_change_password FROM users "
"WHERE LOWER(role) = 'admin' ORDER BY created_at ASC LIMIT 1"
)
row = cur.fetchone()
finally:
conn.close()
if not row:
return {"first_time": True}
still_default = False
try:
still_default = verify_password("admin123", row["hashed_password"])
except Exception:
still_default = False
return {"first_time": bool(row["must_change_password"]) and still_default}
except Exception:
return {"first_time": True}
+5 -1
View File
@@ -151,7 +151,11 @@ async def delete_soundfont(sf_id: str, current_user: dict = Depends(get_current_
@router.get("/soundfonts/download/{sf_id}")
async def download_soundfont_asset(sf_id: str):
clean_id = sf_id.replace("sf_", "") if sf_id.startswith("sf_") else sf_id
for base_dir in [UPLOAD_SF_DIR, SYSTEM_SF_DIR]:
# Cũng tìm trong static/soundfonts (font bundled theo deployment) — trước
# đây chỉ UPLOAD + SYSTEM → font bundled 404 → incognito (IndexedDB rỗng)
# không tải được font → instrument CÂM (browser thường dùng cache nên OK).
static_sf_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "static", "soundfonts")
for base_dir in [UPLOAD_SF_DIR, SYSTEM_SF_DIR, static_sf_dir]:
if not os.path.isdir(base_dir):
continue
# Prefer SF2: the client FluidSynth WASM cannot decode SF3 (Ogg Vorbis)
+6 -1
View File
@@ -81,7 +81,12 @@ async def get_index():
if not os.path.exists(index_path):
return HTMLResponse(content=f"<h1>SonicForge Studio: index.html not found at {index_path}</h1>", status_code=404)
with open(index_path, "r", encoding="utf-8") as file:
return HTMLResponse(content=file.read(), status_code=200)
resp = HTMLResponse(content=file.read(), status_code=200)
# no-cache: index.html PHẢI luôn mới (các bundle JS dùng ?v= để bust) —
# nếu browser cache HTML cũ → stamp cũ → tải bundle cũ (bug "không load
# được bundle mới" ở incognito — cache heuristic không có Cache-Control).
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
return resp
@app.get("/favicon.svg")
+2273 -354
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+99 -13
View File
@@ -23,6 +23,9 @@
let _activeOscillators = {};
let _gainNode = null;
let _pendingOutputDestination = null;
let _outputDestination = null; // cache đích route — dedupe swap dư giữa stream
let _validPercCache = {}; // { sfId: [bank, prog] | null } — preset percussion hợp lệ
let _sfLoadFailAt = {}; // { sfId: timestamp } — cooldown 10s sau load fail
let _scheduledNotes = [];
let _loadPromises = {};
let _sfloadSeq = 0;
@@ -32,7 +35,8 @@
if (!_gainNode) {
_gainNode = _audioCtx.createGain();
_gainNode.gain.value = 0.3;
_gainNode.connect(_pendingOutputDestination || (window.masterBus ? window.masterBus.input : _audioCtx.destination));
_outputDestination = _pendingOutputDestination || (window.masterBus ? window.masterBus.input : _audioCtx.destination);
_gainNode.connect(_outputDestination);
}
return _audioCtx;
}
@@ -41,7 +45,8 @@
if (!_gainNode) {
_gainNode = ctx.createGain();
_gainNode.gain.value = 0.3;
_gainNode.connect(_pendingOutputDestination || (window.masterBus ? window.masterBus.input : ctx.destination));
_outputDestination = _pendingOutputDestination || (window.masterBus ? window.masterBus.input : ctx.destination);
_gainNode.connect(_outputDestination);
}
return ctx;
}
@@ -54,7 +59,8 @@
if (!_gainNode) {
_gainNode = window.__sharedAudioCtx.createGain();
_gainNode.gain.value = 0.3;
_gainNode.connect(_pendingOutputDestination || window.__sharedAudioCtx.destination);
_outputDestination = _pendingOutputDestination || window.__sharedAudioCtx.destination;
_gainNode.connect(_outputDestination);
}
return window.__sharedAudioCtx;
};
@@ -68,9 +74,22 @@
setOutputDestination: function (node) {
try {
if (_gainNode) {
_gainNode.disconnect();
const dest = node || (window.masterBus ? window.masterBus.input : ((_audioCtx || window.__sharedAudioCtx).destination));
// DEDUPE: đích không đổi → KHÔNG disconnect/reconnect.
// Swap dư giữa dòng notes đang phát (applyAllTrackMuteSolo →
// updateSfRouting gọi lại cùng đích sfEntry sau noteon đầu)
// làm ScriptProcessor xuất buffer uninitialized → NaN →
// 11 biquad "state is bad" → CÂM (mọi log: state-bad nổ
// ngay sau setOutputDestination lần 2).
if (dest === _outputDestination) return;
_gainNode.disconnect();
_gainNode.connect(dest);
// Reconnect VU analyser tap (app.jsx VU tick gắn __vuAnalyser
// trên _gainNode): disconnect() không đối số ngắt MỌI kết
// nối kể cả analyser → sfAudioPeak = 0 mãi → track VU MIDI
// không nhảy khi có âm (user bug 06:35).
if (_gainNode.__vuAnalyser) _gainNode.connect(_gainNode.__vuAnalyser);
_outputDestination = dest;
console.log('[SonicSF] setOutputDestination to:', node ? 'track node (sfEntry)' : 'masterBus.input');
} else {
_pendingOutputDestination = node || null;
@@ -104,7 +123,8 @@
if (!_gainNode) {
_gainNode = _audioCtx.createGain();
_gainNode.gain.value = 0.3;
_gainNode.connect(_pendingOutputDestination || (window.masterBus ? window.masterBus.input : _audioCtx.destination));
_outputDestination = _pendingOutputDestination || (window.masterBus ? window.masterBus.input : _audioCtx.destination);
_gainNode.connect(_outputDestination);
}
console.log("[SonicSF] AudioCtx state:", _audioCtx.state, "sampleRate:", _audioCtx.sampleRate);
@@ -140,7 +160,7 @@
// expected notice when a soundfont simply has no
// preset for a bank (e.g. bank 128 on a melodic-only
// font) — the note is just silent, not an error.
if (msg && msg.indexOf('No preset found on channel') !== -1) return;
if (msg && (msg.indexOf('No preset found on channel') !== -1 || msg.indexOf('There is no preset with bank number') !== -1)) return;
console.warn('[FluidSynth:err]', msg);
}
});
@@ -191,17 +211,31 @@
var spn = _audioCtx.createScriptProcessor(spBufSz, 0, 2);
var lp = _fluidModule._malloc(spBufSz * 4);
var rp = _fluidModule._malloc(spBufSz * 4);
// Heap WASM có thể realloc khi load SoundFont lớn (SGM-V2.01
// ~300MB) → lp/rp DANGLE → đọc vùng nhớ đã free → NaN/garbage
// → master chain "state is bad" → CÂM + stuck. Theo dõi
// buffer + re-malloc khi đổi.
var _heapBufRef = _fluidModule.HEAPU8.buffer;
spn.onaudioprocess = function (e) {
var left = e.outputBuffer.getChannelData(0);
var right = e.outputBuffer.getChannelData(1);
var sz = left.length;
try {
if (_fluidModule.HEAPU8.buffer !== _heapBufRef) {
try { _fluidModule._free(lp); _fluidModule._free(rp); } catch (er2) {}
lp = _fluidModule._malloc(sz * 4);
rp = _fluidModule._malloc(sz * 4);
_heapBufRef = _fluidModule.HEAPU8.buffer;
}
_fluidModule._fluid_synth_write_float(_synthPtr, sz, lp, 0, 1, rp, 0, 1);
var hf = _fluidModule.HEAPF32;
var lpb = lp >> 2, rpb = rp >> 2;
for (var si = 0; si < sz; si++) {
left[si] = hf[lpb + si];
right[si] = hf[rpb + si];
// NaN sweep: mẫu NaN/Inf → 0 (chain biquad
// KHÔNG BAO GIỜ được nhận NaN → không state-bad).
var L = hf[lpb + si], R = hf[rpb + si];
left[si] = isFinite(L) ? L : 0;
right[si] = isFinite(R) ? R : 0;
}
} catch (er) {}
};
@@ -298,8 +332,15 @@
var url = "/api/v1/plugins/soundfonts/download/" + encodeURIComponent(sfId) + "?t=" + Date.now();
var resp = await fetch(url);
if (!resp.ok) {
console.warn("[SonicSF] SoundFont not found:", sfId);
return false;
// Fallback: font bundled theo deployment (static/soundfonts —
// serve qua /soundfonts/{f} — catalog default-soundfonts).
var url2 = "/soundfonts/" + encodeURIComponent(sfId.replace(/^sf_/, '')) + "?t=" + Date.now();
var resp2 = await fetch(url2);
if (!resp2.ok) {
console.warn("[SonicSF] SoundFont not found:", sfId);
return false;
}
resp = resp2;
}
buf = await resp.arrayBuffer();
if (cache) await cache.saveBuffer(sfId, buf);
@@ -515,10 +556,24 @@
// Quick instrument pick on a track does not pre-load it, so load
// lazily here and retry the note once the font is ready.
if (finalSfId && !_sfHandleMap.has(finalSfId)) {
// Cooldown lỗi: font 404 → KHÔNG spam fetch mỗi note (10s)
// — note chạy thẳng fallback để CÓ ÂM.
var _lastFail = _sfLoadFailAt[finalSfId] || 0;
if (Date.now() - _lastFail < 10000) {
try { self._playNoteFallback(note, velocity, durationMs, startTime, program, null, channel, synthEngine); } catch (e) {}
return;
}
console.log('[SonicSF] soundfont not loaded yet, loading:', finalSfId);
self.loadSoundFont(finalSfId).then(function (ok) {
console.log('[SonicSF] loadSoundFont result:', ok, 'for:', finalSfId);
if (ok) doNote();
if (ok) {
doNote();
} else {
// Font KHÔNG tải được (404/format) → KHÔNG drop note
// câm lặng ("bỏ qua WASM") — fallback oscillator.
_sfLoadFailAt[finalSfId] = Date.now();
try { self._playNoteFallback(note, velocity, durationMs, startTime, program, null, channel, synthEngine); } catch (e) {}
}
});
return;
}
@@ -535,7 +590,34 @@
console.log('[SonicSF] selectProgram for channel:', ch, 'sfHandle:', sfHandle, 'bank:', finalBank, 'prog:', finalProg);
if (sfHandle !== undefined) {
try {
_fluidModule._fluid_synth_program_select(_synthPtr, ch, sfHandle, finalBank, finalProg);
// Percussion (bank 128): tìm preset HỢP LỆ trong
// font — quét bank 128 + bank 0 (0-127) MỘT LẦN,
// cache theo sfId. Trước đây chỉ thử 4 preset cố
// định → font không có → cache channel = (128,0)
// INVALID → note sau skip re-select (progAlreadySet)
// → noteon preset rỗng = CÂM ("1 âm đầu rồi câm").
if (finalBank === 128) {
var _vKey = finalSfId || ('h' + sfHandle);
if (_validPercCache[_vKey] === undefined) {
var _found = null;
for (var _b = 0; _b < 2 && !_found; _b++) {
var _bk = _b === 0 ? 128 : 0;
for (var _p = 0; _p < 128 && !_found; _p++) {
try {
if (_fluidModule._fluid_synth_program_select(_synthPtr, 9, sfHandle, _bk, _p) === 0) {
_found = [_bk, _p];
}
} catch (e) {}
}
}
_validPercCache[_vKey] = _found;
}
if (_validPercCache[_vKey]) {
finalBank = _validPercCache[_vKey][0];
finalProg = _validPercCache[_vKey][1];
}
}
var _selRet = _fluidModule._fluid_synth_program_select(_synthPtr, ch, sfHandle, finalBank, finalProg);
} catch (e) {}
} else {
try { _fluidModule._fluid_synth_bank_select(_synthPtr, ch, finalBank); } catch (e) {}
@@ -618,7 +700,11 @@
var oscType = 'triangle';
var attackTime = 0.03, decayTime = 0.1, sustainLevel = 0.5, releaseTime = 0.2, volFactor = 0.25;
var prog = program !== undefined ? parseInt(program) : 0;
if (channel !== undefined && channel >= 0 && channel < 16) {
// CHỈ dùng cache channel khi KHÔNG có program/synthEngine được
// truyền — trước đây override program của track bằng cache channel
// (bị track khác cùng channel ghi đè → preview note vẽ mới mang
// nhạc cụ của track TRƯỚC).
if (program === undefined && channel !== undefined && channel >= 0 && channel < 16) {
prog = _channels[channel].program || prog;
}
if (prog >= 0 && prog <= 7) { oscType = 'sine'; decayTime = 0.3; sustainLevel = 0.1; releaseTime = 0.2; }
+2 -2
View File
@@ -16,7 +16,7 @@
<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/soundfontStorage.js?v=202607271016"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202608042158"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202608070635"></script>
<script src="/static/js/services/aiGateway.js?v=202608037200"></script>
<script src="/static/js/services/dawCommandDispatcher.js?v=202607271016"></script>
<script src="/static/js/services/pianoRollTabService.js?v=202607272044"></script>
@@ -24,7 +24,7 @@
<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/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608042300" defer></script>
<script src="/static/js/app.precompiled.js?v=202608070950" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {
+1071
View File
File diff suppressed because it is too large Load Diff