Player Piano: Maple Leaf Rag
by ericb
On this day 154 years ago, Scott Joplin was born. Happy birthday to the King of Ragtime!
Here's a rendition of his famous Maple Leaf Rag, with accompanying animation.
I've been trying to learn this piece on piano for a while. At some point I thought it might be
fun to try transcribing it in PICO-8, and this is the result! Information on the music and
animation is below:
Music
I tried to stay as true to the score as possible - since I was limited to only 4 channels, I
had to make creative use of instruments and effects. So even though it's all just piano, there
are 5 different instruments used:
normal held note
held note that's one octave down (for the low note at the start of the run that goes all the
way up the piano)
held note with simultaneous octave down (using detune)
staccato note
staccato note with simultaneous octave down (using detune)
Since this piece makes heavy use of octaves in the bass and the accented parts of the melody,
the detune trick was a huge help in getting all of the notes in (there are a lot of notes!).
The detune emphasizes the lower note over the higher one, but I think it sounds convincing
enough. Even with this trick, there were a handful of places I had to leave a note out of a
chord, but I tried to leave out notes which were the least important/noticeable (eg. repeated
nearby in either the bass or melody, or notes less important to the character of a chord, like
prioritizing the 3rd/7th over the 5th). I think unless you know this piece really well and
closely watch the animation, it would be pretty hard to notice the missing notes.
There weren't quite enough sfx slots to fit the last section of the piece, so I ended up
arranging that in a separate cart and then putting it into the map data using CSTORE().
Partway through playback, I swap out some sfx that are no longer needed for the ones used in
the last section.
Animation
The animation is all dynamic, based on the SFX data. Which is nice, because once I got it
working I could change the sfx or add new parts and the animation would continue to reflect
the music.
As for how it works, I wrote a function GETNOTES() which takes as parameters a time offset and
returns a list of the notes that would be playing. I'm using STAT(54) to get the index of the
currently playing pattern, and STAT(56) to get the current tick within it. With that
information, I can add the time offset and use PEEK() to determine which pattern/sounds would
be playing at the requested time and get the note information. By checking the pitch along
with which instrument voice is playing a given note, I can figure out which keys should be
pressed. For the detune notes that means playing two keys at once an octave apart. For
staccato notes or notes with the fade out effect the key lets off earlier in the beat.
For the animation of the actual keys, I call GETNOTES() without a time offset to get the
currently playing notes. Each of the keys is rendered with a sprite, and we can look up
whether the key's note is being played to determine whether we should draw its pressed or
unpressed sprite.
For the piano roll, I call GETNOTES() with a different time offset for each line, and for all
the notes returned I use PSET() to change the colour at the coresponding spot on the roll
(adjusting for perspective and scaling so the 88 notes of the piano span 44 horizontal
pixels). Since it's all offset based on what's currently playing, doing this every frame
naturally makes the dots scroll down the screen as if the roll is moving.
Other thoughts
If you'd like to see how it works in more detail, the code is reasonable well-commented so
take a peek inside :) You could probably use a similar approach to automatically synchronize
things in a rhythm game.
I also think it would be cool to transcribe more pieces of piano music and make this into a
series -- you could even fit multiple songs in a single cart by using PX9 compression or
similar, since the current code doesn't use very many tokens/characters. But it took me quite
a while to transcribe just this one piece, and styles with more sustained notes / big chords
wouldn't work well with PICO-8's limitations, so I'll leave it at this for now.