FIX: sửa lỗi parse file midi có track hoặc chunk phụ trợ

This commit is contained in:
2026-07-30 17:21:42 +07:00
parent 1de69b8f4e
commit 402cac56a3
2 changed files with 139 additions and 7 deletions
+135 -4
View File
@@ -14852,11 +14852,142 @@ const App = () => {
}));
};
// MIDI .mid file parser
const parseMidiFile = (arrayBuffer) => {
const data = new Uint8Array(arrayBuffer);
if (data.length < 14) return null;
var pos = 0;
var read32 = function() { var v = (data[pos] << 24) | (data[pos+1] << 16) | (data[pos+2] << 8) | data[pos+3]; pos += 4; return v; };
var read16 = function() { var v = (data[pos] << 8) | data[pos+1]; pos += 2; return v; };
var readVLQ = function() { var v = 0, b; do { b = data[pos++]; v = (v << 7) | (b & 0x7f); } while (b & 0x80); return v; };
var header = String.fromCharCode(data[0], data[1], data[2], data[3]);
if (header !== 'MThd') return null;
read32(); var fmt = read16(); var numTracks = read16(); var division = read16();
var ticksPerBeat = (division & 0x8000) ? 480 : (division || 480);
var bpm = 120;
var result = [];
for (var t = 0; t < numTracks; t++) {
if (pos + 8 > data.length) break;
var trkId = String.fromCharCode(data[pos], data[pos+1], data[pos+2], data[pos+3]);
pos += 4; var trkLen = read32(); var endPos = Math.min(pos + trkLen, data.length);
if (trkId !== 'MTrk') {
pos = endPos;
continue;
}
var absTicks = 0; var runningStatus = 0; var trackName = 'MIDI Track ' + (t + 1);
var midiNotes = []; var pendingNotes = {};
var maxAbsTick = 0;
while (pos < endPos) {
try {
var delta = readVLQ(); absTicks += delta;
var status = data[pos];
if (status >= 0x80) {
if (status < 0xf0) {
runningStatus = status;
}
pos++;
} else {
status = runningStatus;
}
var cmd = status >> 4;
if (cmd === 0x9 || cmd === 0x8) {
var chan = status & 0x0F;
var pitch = data[pos++]; var vel = pos < endPos ? data[pos++] : 0;
var noteKey = chan + '_' + pitch;
if (cmd === 0x9 && vel > 0) {
pendingNotes[noteKey] = { tick: absTicks, vel: vel };
if (absTicks > maxAbsTick) maxAbsTick = absTicks;
} else {
var pn = pendingNotes[noteKey];
if (pn) {
var durTicks = absTicks - pn.tick;
if (durTicks <= 0) durTicks = 240;
midiNotes.push({ id: 'mn_' + t + '_' + pitch + '_' + pn.tick, pitch: pitch, start_beat: pn.tick / ticksPerBeat, duration_beats: durTicks / ticksPerBeat, velocity: Math.min(1, pn.vel / 127) });
delete pendingNotes[noteKey];
}
}
} else if (status >= 0xc0 && status < 0xe0) { if (pos < endPos) pos += 1; }
else if (status >= 0xe0 && status < 0xf0) { if (pos + 2 <= endPos) pos += 2; }
else if (status >= 0xa0 && status < 0xc0) { if (pos + 2 <= endPos) pos += 2; }
else if (status >= 0xf0 && status < 0xf8) { if (pos < endPos) { var sl = readVLQ(); pos += sl; } }
else if (status === 0xff) {
if (pos >= endPos) break;
var metaType = data[pos++]; var metaLen = readVLQ();
if (metaType === 0x03) { try { trackName = String.fromCharCode.apply(null, Array.from(data.subarray(pos, pos + metaLen))); } catch(e) {} }
else if (metaType === 0x51 && pos + 3 <= endPos) { bpm = Math.round(60000000 / ((data[pos] << 16) | (data[pos+1] << 8) | data[pos+2])); }
pos += Math.min(metaLen, endPos - pos);
}
else { if (pos + 2 <= endPos) pos += 2; }
} catch(e) { pos = endPos; }
}
Object.keys(pendingNotes).forEach(function(k) {
var pn = pendingNotes[k];
var parts = k.split('_');
var p = parseInt(parts[1]);
var dur = Math.max(240, maxAbsTick - pn.tick);
midiNotes.push({ id: 'mn_' + t + '_' + p + '_' + pn.tick, pitch: p, start_beat: pn.tick / ticksPerBeat, duration_beats: dur / ticksPerBeat, velocity: Math.min(1, pn.vel / 127) });
});
if (midiNotes.length > 0) {
var lastEnd = 0;
midiNotes.forEach(function(n) { var e = (n.start_beat + n.duration_beats) * 60 / bpm; if (e > lastEnd) lastEnd = e; });
result.push({ name: trackName, notes: midiNotes, duration: lastEnd || 4, startTime: 0, id: 'midi_' + t + '_' + Date.now() });
}
pos = endPos;
}
return result.length > 0 ? result : null;
};
// Load File on Track (with server upload)
const loadFileOnTrack = async (trackId, file) => {
if (!file) return;
showToast(`Đang nạp file ${file.name}...`, 'info');
var fileName = file.name || '';
var isMidi = /\.mid$|\.midi$/i.test(fileName);
showToast(`Đang nạp file ${fileName}...`, 'info');
try {
if (isMidi) {
var arrayBuffer = await file.arrayBuffer();
var midiResult = parseMidiFile(arrayBuffer);
if (!midiResult || midiResult.length === 0) {
showToast('Không tìm thấy nốt nhạc trong file MIDI.', 'error');
return;
}
if (midiResult.length === 1) {
updateActiveTracks(prev => prev.map(function(t) {
if (t.id !== trackId) return t;
return { ...t, name: fileName, midiItems: [midiResult[0]] };
}));
showToast('Đã tải MIDI: ' + fileName, 'success');
} else {
var colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309', '#ca8a04', '#dc2626', '#0891b2'];
var firstDone = false;
midiResult.forEach(function(midiItem, idx) {
if (!firstDone) {
updateActiveTracks(prev => prev.map(function(t) {
if (t.id !== trackId) return t;
return { ...t, name: midiItem.name || fileName, midiItems: [midiItem] };
}));
firstDone = true;
} else {
var newId = 'midi_track_' + Date.now() + '_' + idx;
updateActiveTracks(function(prev) {
var curLen = prev.length;
return prev.concat([{
id: newId, name: midiItem.name || 'MIDI Track ' + (idx + 1),
buffer: null, startTime: 0, volumeDb: 0, pan: 0,
muted: false, solo: false, color: colors[idx % colors.length],
markers: [], serverFileId: null, clips: [], sections: [],
midiItems: [midiItem],
isArmed: false, monitoringEnabled: true,
inputSource: { deviceType: 'NONE', deviceId: '' }
}]);
});
}
});
showToast('Đã tải MIDI: ' + midiResult.length + ' tracks from ' + fileName, 'success');
}
return;
}
// Upload to server
uploadToServer(file, trackId);
@@ -14867,13 +14998,13 @@ const App = () => {
} = await window.SonicAudio.decodeAudioFile(file);
setTracks(prev => prev.map(t => t.id === trackId ? {
...t,
name: file.name,
name: fileName,
buffer: decodedBuffer,
channelInfo: channelInfo
} : t));
showToast(`Nạp file thành công: ${file.name} (${channelInfo.label})`, 'success');
showToast(`Nạp file thành công: ${fileName} (${channelInfo.label})`, 'success');
} catch (err) {
showToast("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');
}
};