I’m currently developing my first game for a course using PixiJS and TypeScript, and I decided to make it a rhythm game. The gameplay is very simple: the user has to press one of six keys when said key falls within a certain area of the screen. However, the backend for the gameplay is a bit more complex, since the songs for the game are supposed to be chosen by the user from a .mid file (which is intended to represent the notes that show up onscreen) and an .ogg file (which is the music that will be played).
Now, I’ve used a library to parse a custom .mid file into a .json, and then from that .json I can get the data to show the notes onscreen as keys for the user to press. The issue, however, is that no matter the formulas I’ve tried or the many iterations my function to parse the json into a playable array I’ve gone through, what appears on screen ALWAYS ends up desynced with the music.
function processMidiJson(json: any) {
console.log('Processing MIDI JSON:', json);
const jsonArray = convertArraysToObjectArrays(Object.values(json));
let notesArray: [string, number][] = [];
const tempo = json.tracks[0][1].setTempo.microsecondsPerQuarter; // Tempo in microseconds per quarter note
const bpm = 60000 / (tempo / 1000);
const division = json.division; // Division value from MIDI file
const msPerTick = 60000 / (bpm * division)
console.log(msPerTick);
for (let i = 0; i < jsonArray[2][1].length; i++) {
const currentObject = jsonArray[2][1][i];
if (currentObject.noteOn) {
if (jsonArray[2][1][i - 1].noteOn) { // If the previous note is a noteOn, then it is a chord and only the delta of one note is necessary.
notesArray.push([String(currentObject.noteOn.noteNumber - 96), ((currentObject.delta) * msPerTick)]);
} else {
notesArray.push([String(currentObject.noteOn.noteNumber - 96), ((currentObject.delta + jsonArray[2][1][i + 1].delta) * msPerTick)]);
}
}
}
console.log(notesArray);
return notesArray;
}
This is my current function. The .mid file I’m using is parsed into this json, and the json is parsed into this array, in which the first value of each array is the note number (which is tied to a key) and the second value the gap between the current and the previous note in milliseconds. Now, I’ve tried doing both currentObject.delta + jsonArray[2][1][i + 1].delta
and currentObject.delta + jsonArray[2][1][i - 1].delta
, and using the delta value of the current note’s noteOff seems to work better, but in both cases it still gets desynced the further you get into the song.
I’ve kind of run out of ideas at this point, it’s clear that MIDI files can be rather inconsistent, but the values in ms that I get should nevertheless match the music being played (which is, by the way, a 1:1 representation of the midi file since it was created straight from the midi being used). Considering that the testing song I’m using has 120 BPM, the values in the array seem to be correct, such as a 1/8th note lasting 250ms and a dotted quarter note lasting 750ms, nevertheless it gets desynced.
This is the GitHub repository in case anyone wants to test it first-hand.
Juani is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.