我是 JavaScript 的初学者,但我有 1 年的 Python、C/C++ 和 Java 编程经验(自学)。
我的目标是将 MIDI 文件按顺序合并(连接)为一个长 MIDI 文件,以便实现无缝循环播放。所有 MIDI 文件都是预制的、统一的(相同数量的轨道、相同的乐器、相同的通道),因为每个文件只包含一小节音乐。拍号不同。
据我了解,MIDI 文件是用十六进制编写的,有 MThd 和 MTrk 块以及所有乐器轨道数据。我的所有文件都包含 5 个轨道 - 1 个元数据和 4 个乐器。我假设每个 MIDI 文件都应该保留元轨道以保留拍号。如果我理解正确,则 MThd 块应仅保留在第一个文件中,并且应从其余文件中省略,因为轨道数据应按顺序附加到序列的第一个文件的相应轨道中。
我尝试查找如何执行此操作的信息,但找不到任何指南或解释,因此我被迫使用 ChatGPT 生成一些示例功能,只是为了有一个起点:
当然,它根本不起作用,我无法真正理解为什么以及如何解决它,所以如果有人可以向我解释如何解决我的问题以及在哪里可以找到有关该主题的信息,我将不胜感激。
async function fetchMidiFile(url) {
console.log(`Fetching MIDI file: ${url}`);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch MIDI file from ${url}`);
}
const arrayBuffer = await response.arrayBuffer();
const midiData = new Uint8Array(arrayBuffer);
console.log(`Fetched MIDI file (${midiData.length} bytes):`, midiData);
return midiData;
} catch (error) {
console.error(`Error fetching MIDI file: ${error}`);
return null;
}
}
async function mergeMidiFilesSequentially(midiFiles) {
if (midiFiles.length === 0) {
throw new Error("No MIDI files provided.");
}
const mergedMidi = [];
const trackChunks = []; // To store each track's combined data
let headerChunk = null;
midiFiles.forEach((file, index) => {
let offset = 0;
while (offset < file.length) {
const chunkType = file.slice(offset, offset + 4); // Get chunk type (MThd or MTrk)
const chunkSizeBytes = file.slice(offset + 4, offset + 8); // Get chunk size bytes
const chunkSize = (chunkSizeBytes[0] << 24) | (chunkSizeBytes[1] << 16) | (chunkSizeBytes[2] << 8) | chunkSizeBytes[3];
const chunkData = file.slice(offset + 8, offset + 8 + chunkSize);
// Handle the header chunk (MThd)
if (
chunkType[0] === 0x4D &&
chunkType[1] === 0x54 &&
chunkType[2] === 0x68 &&
chunkType[3] === 0x64 // "MThd"
) {
if (index === 0) {
// Use the first file's header
headerChunk = [...chunkType, ...chunkSizeBytes, ...chunkData];
}
}
// Handle track chunks (MTrk)
else if (
chunkType[0] === 0x4D &&
chunkType[1] === 0x54 &&
chunkType[2] === 0x72 &&
chunkType[3] === 0x6B // "MTrk"
) {
// Append the track's events to the respective position
if (!trackChunks[index]) {
trackChunks[index] = [];
}
trackChunks[index].push(...chunkData);
}
offset += 8 + chunkSize; // Move to the next chunk
}
});
if (!headerChunk) {
throw new Error("No valid header chunk found in the MIDI files.");
}
// Add the header chunk to the merged MIDI file
mergedMidi.push(...headerChunk);
// Concatenate the tracks sequentially
trackChunks.forEach((trackData) => {
const concatenatedData = trackData.reduce((acc, data) => acc.concat(data), []);
const trackSize = concatenatedData.length;
// Create MTrk chunk
mergedMidi.push(
0x4D, 0x54, 0x72, 0x6B, // Chunk type "MTrk"
(trackSize >> 24) & 0xff, (trackSize >> 16) & 0xff, (trackSize >> 8) & 0xff, trackSize & 0xff, // Chunk size
...concatenatedData
);
});
return new Uint8Array(mergedMidi);
}
async function generateBackingTrack() {
console.log("Generate Backing Track button clicked!");
const progressBar = document.getElementById("progressBar");
if (!progressBar) {
console.error("Progress bar element is missing.");
return;
}
const bars = document.querySelectorAll(".bar");
const totalBars = bars.length;
const combinedTracks = [];
for (let i = 0; i < totalBars; i++) {
try {
const chordRoot = document.getElementById(`chordRoot${i + 1}`).value;
const chordType = document.getElementById(`chordType${i + 1}`).value;
const genre = document.getElementById("genre").value;
const timeSignature = document.getElementById("globalTimeSignature").value;
const midiKey = `${genre}-${chordRoot.toLowerCase()}-${chordType.toLowerCase()}-${timeSignature}`;
const midiFilePath = hardcodedMidiFiles[midiKey];
if (!midiFilePath) {
console.error(`MIDI file not found for: ${midiKey}`);
continue;
}
const midiData = await fetchMidiFile(midiFilePath);
if (midiData) {
combinedTracks.push(midiData);
} else {
console.error(`Failed to load MIDI file: ${midiKey}`);
}
const progress = Math.round(((i + 1) / totalBars) * 100);
progressBar.style.width = `${progress}%`;
} catch (err) {
console.error(`Error processing bar ${i + 1}:`, err);
}
}
if (combinedTracks.length > 0) {
const mergedMidi = mergeMidiFilesSequentially(combinedTracks);
alert("Backing track generated successfully!");
const blob = new Blob([mergedMidi], {
type: "audio/midi"
});
const url = URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = "generated-backing-track.mid";
downloadLink.click();
URL.revokeObjectURL(url);
} else {
console.error("No valid MIDI files to merge.");
}
}
这是有效的,我在自己的项目中测试了它:https://github.com/sergi/jsmidi