How to Sequentially Combine MIDI Files Using JavaScript

I’m new to JavaScript, with a year of self-taught programming experience in Python, C/C++, and Java. I aim to sequentially combine MIDI files into a single, continuous MIDI file for uninterrupted playback. Each of my MIDI files is consistent, featuring the same number of tracks and instruments, with each file containing precisely one bar of music, though the time signatures differ. I understand that MIDI files are structured with hex code, consisting of MThd and MTrk chunks along with instrument track information. My MIDI files have five tracks total: one metadata track and four instrument tracks. I believe it’s essential to keep the metadata from the first file to maintain the time signature. I’ve created sample MIDI files along with an expected output file for analysis to determine the necessary changes. I need guidance on how to carry out this merging process and any resources for further understanding.

To sequentially combine MIDI files, you can indeed use JavaScript and libraries like midi-file. Let me provide a slightly different approach that ensures you maintain full control over time signature changes and event timing.

Steps to Combine MIDI Files

  1. Install necessary packages:
npm install midi-file
  1. Parse the MIDI files:
const fs = require('fs');
const midiFile = require('midi-file');

// Helper function to parse MIDI files
const parseMIDI = (filePath) => {
    const content = fs.readFileSync(filePath);
    return midiFile.parseMidi(content);
};

const midi1 = parseMIDI('path-to-first-midi.mid');
const midi2 = parseMIDI('path-to-second-midi.mid');
// Add more MIDI files as needed
  1. Combine MIDI tracks adjusting deltaTimes to accommodate for timing differences:
let combinedTracks = [...midi1.tracks];
let offsetTime = 0;

// A utility to adjust timing based on bar length, specific to your needs
const adjustTrackTiming = (secondTracks, offsetTime) => {
    // Iterate through all tracks except metadata
    secondTracks.slice(1).forEach(track => {
        const adjustedTrack = track.map(event => {
            // Modify event delta time as needed
            event.deltaTime += offsetTime;
            return event;
        });
        combinedTracks.push(adjustedTrack);
    });
};

adjustTrackTiming(midi2.tracks, offsetTime);

const combinedMIDI = {
    header: midi1.header, // Retain metadata from the first file
    tracks: combinedTracks
};

const outputMIDI = midiFile.writeMidi(combinedMIDI);
fs.writeFileSync('combined-midi-file.mid', new Uint8Array(outputMIDI));

This approach not only combines the tracks but also considers timing issues due to differing time signatures. You may need to calculate and adjust deltaTime manually to ensure seamless playback, respecting the unique length of each bar.

For further exploration, consider diving into the MIDI file specifications to fully understand timing and event structures, or use visualization tools like MIDI editors to examine how your files align before and after modification.

Combining MIDI files in JavaScript requires reading and manipulating the binary structure of each file. Here's a simplified approach using the midi-file library to help achieve this task:

  1. Install the necessary libraries via npm:
npm install midi-file
  1. Load and parse each MIDI file using fs and midi-file libraries.
const fs = require('fs');
const midiFile = require('midi-file');

// Read the first MIDI file
const firstMIDI = fs.readFileSync('first-midi-file.mid');
const firstParsed = midiFile.parseMidi(firstMIDI);

// Read the second MIDI file
const secondMIDI = fs.readFileSync('second-midi-file.mid');
const secondParsed = midiFile.parseMidi(secondMIDI);
  1. Combine the tracks, using the time signature and metadata from the first file:
const combinedTracks = [...firstParsed.tracks];
// Skip the metadata track in subsequent files
secondParsed.tracks.slice(1).forEach(track => { 
    combinedTracks.push(track);
});

const combinedMIDI = {
    header: firstParsed.header,
    tracks: combinedTracks
};

const mergedMIDI = midiFile.writeMidi(combinedMIDI);
fs.writeFileSync('combined-midi-file.mid', new Uint8Array(mergedMIDI));

This code snippet keeps the metadata from the first file intact and appends the instrument tracks from subsequent MIDI files. Given the differences in time signatures, ensure to adjust deltaTimes if required for synchronization.

Consider these additional resources to deepen your understanding: Tone.js MIDI or the Web Audio Modules documentation.

By following this practical approach, you will efficiently combine your MIDI files while preserving the original metadata.