Recently I stumbled over the extracted files of several songs from one of my all time favorite video games: Rock Band 2.
If you are unlucky enough to never have played it, the game basically shows scrolling musical “notes” while players – using fake instruments and a microphone – have to match them. And it’s loads of fun! I wrote a little toy program in Unity that can read and display these songs. This article is about reading the MIDI files and reproducing the corresponding notes and text on the screen.
Reading Rockband Files
First let’s start with a look at the files I found. At the risk of being sued into oblivion by Lars Ulrich, here is the song “Battery” by Metallica:

There are four audio files – each containing the isolated audio for one of the playable instruments: drums, guitar, bass (“rythm.ogg”) and vocals (“song.ogg”). The latter might also contain some general sounds like non-playable background instruments (e.g. a second guitar).
The “Song.ini” file just contains some meta data like the song title and artist. The most interesting file is called “Notes.mid”.
While “Notes.mid” is indeed a MIDI file that contains notes, you will have a pretty bad time listening to it:
If you know the original song it might sound remotely familiar, but you will notice that the notes are all over the place and don’t match the real song’s notes at all. That’s because the MIDI format is “abused”. There a few separate “bands” of notes for guitar, bass and drums, each about 5 notes wide (or high). The Rockband instruments also happen to have 5 buttons. So what could that mean? 🤓
For each difficulty that is selectable in the game (from “easy” to “expert”) there is one range of notes used to represent the 5 instrument buttons in a way that approximates the real notes. So for example D4, F4, E4, G4, A4 for “easy”. With that information it’s easy enough to parse the song notes.
MIDI files consist of so called “events”. Most important for reading Rockband files are “note on”, “note off” events. They generally contain a timestamp in form of a “tick”, the note value, velocity (beats per minute) and a track name. More about these values later. Let’s first look at tracks.
Tracks
The MIDI events are organized into a number of separate tracks which are used for various things. There are still some events/notes on these tracks whose purpose I don’t know. But for the purpose of displaying the notes and lyrics I found everything I needed.
Track Name | Purpose |
PART GUITAR | Notes for the guitar controller |
PART BASS | Notes for the bass controller |
PART DRUMS | Notes for the drumkit controller |
PART VOCALS | Vocal notes and lyrics |
EVENTS | In-game events, e.g. [music_start] |
VENUE | Camera, light and audience control |
BEAT | One note for each measure, so basically “1, 2, 3, 4, 1, 2, 3, 4, …” depending on the beat |
Track 0 (song title) | Tempo and beat changes |
Notes
To read the notes that I want to display on the screen four of the tracks listed above are interesting: The tracks for guitar, bass and drum notes are obvious. But there’s also track 0, usually named after the song title.
Track 0 – Beat and Tempo
Track zero contains beat and tempo changes. To show what that means, this is an example of the two events that usually can be found at tick 0 – the beginning of the song:


The tempo change event at tick 0 – the beginning of the song – sets the tempo to 108 beats per minute. The time signature event sets the “music time”, in this case “four-four time”.
Ticks are the time unit used in MIDI files which is relative to tempo. Since tempo can change at any time in a song, calculating the clock time at which a note/event should appear on the screen requires keeping tracking of all tempo change and time signature events. The following codes shows how I calculate the tick values to clock time:
// first a data structure to keep track of the temp change events internal struct Tempo { // Ticks since last tempo event public long TickDelta; // Ticks since beginning of song public long Tick; // Tempo in beats per minute starting from this tick on public float BeatsPerMinute; // Elapsed time in seconds since beginning of song public float ElapsedRealtime; // Elapsed time in seconds since last tempo event public float ElapsedRealtimeDelta; } // converts ticks to seconds for a given tempo in beats per minute float RelativeTickToElapsedTime(long tick, float bpm) { return bpm > 0 ? tick * 60.0f / (bpm * Midi.Division) : 0; } // called once at the beginning for a song void RecordTempoChanges() { var prev = new Tempo(); foreach (var e in GetTrack<TempoMetaMidiEvent>().Events.OfType<TempoMetaMidiEvent>()) { var tempo = new Tempo() { BeatsPerMinute = 60000000f / e.Value, TickDelta = e.DeltaTime, ElapsedRealtimeDelta = RelativeTickToElapsedTime(e.DeltaTime, prev.BeatsPerMinute), }; tempo.Tick = prev.Tick + tempo.TickDelta; tempo.ElapsedRealtime = prev.ElapsedRealtime + tempo.ElapsedRealtimeDelta; _tempos.Add(tempo); prev = tempo; } } // using the recorded tempo changes this computes the tempo and wall-time for any note's tick internal Tempo GetTempo(long tick) { // ticks and time since the last tempo change Tempo latest = _tempos.Last(t => t.Tick < tick); long tickDelta = tick - latest.Tick; float elapsedRealtimeDelta = RelativeTickToElapsedTime(tickDelta, latest.BeatsPerMinute); return new Tempo() { BeatsPerMinute = latest.BeatsPerMinute, ElapsedRealtime = latest.ElapsedRealtime + elapsedRealtimeDelta, ElapsedRealtimeDelta = elapsedRealtimeDelta, TickDelta = tickDelta, Tick = tick }; }
If you have read until here… well this is embarrasing. This is still a draft. I only published it to push myself to finish it already! Come back later to find the finished version.