Creating chiptunes with Python, MIDI and LSDJ
Like any human being I love music. Like any nerd I love music made on old video game consoles.
In order to make music on my old video game console of choice, the Nintendo Gameboy, I use a piece of software called Little Sound DJ (LSDJ).
LSDJ, to put it very basically allows you to program notes into phrases (which are essentially bars of music) and then create chains of phrases which then become a song.
Working with limitations
The Gameboy only supports four channels (there is a fifth, but it’s hard to work with), two pulse channels, one wave and one noise channel and while it can play all four channels at once the individual channels themselves are monophonic.
These limitations are actually what drew me to chiptune music, the way you have to create your own means of implementing simple musical structures such as chords or non-common-time time signatures.
LSDJ offers tools to compensate for this lack of functionality, for instance a 9/8 time signature can be achieved by having two phrases of 16 notes with the second phrase ending after the 2nd note (so it’s 18/16) and chords by playing the notes in the chords very fast.
Chiptune artists aren’t just limited by the musical tooling available to them however, there’s also the limited resources of the hardware too.
LDSJ can support up to 254 16-note phrases and 254 16-phrase chains, this is actually plenty for most needs but with the 9/8 example above as two phrases are needed it’s suddenly halved to 127 phrases and if we then assume all four channels will be used that’s only 31 phrases per instrument, or 558 notes.
LSDJ again offers some tools to work with this limitation, when adding a phrase to a chain there’s the option to transpose the notes so you can maximise phrase re-use for things like key changes.
Stepping up my game
The majority of the songs I’ve composed / programmed in LSDJ have been relatively straight forward, being either in common time or having simple song structures.
However, I decided to give myself a challenge recently and attempt a cover of Culinary Hypersensitivity by Necrophagist, a song that I’ve been learning on guitar for the best part of a year.
My original attempt at the start of the year didn’t get very far due to me being unable to program the song in one session and losing my place after having time to pick it back.
I was learning the guitar parts and transcribing the Gameboy parts from a MIDI file so decided to try and relay the MIDI data from Logic to LDSJ via the USB-Boy interface I have, but I could never get it to work properly (this is more a Logic issue than the interface though).
I’d previously worked with MIDI data and decided to revisit my previous project but instead of visualising MIDI data I’d instead convert it into the notes, phrases and chains needed to transcribe the song to LSDJ.
I’m plotting all the right notes, just not necessarily in the right order
MIDI data is just a series of events with the only means of determining a musical structure by resolving the MIDI ticks to a musical note value such as a semi-quaver (16th note).
However music isn’t written as a streaming series of notes, it’s instead constructed of bars which contain a series of notes that resolve to a certain measure.
Luckily MIDI stores time signature events along with the ticks they occur at so in order to lay out the blue print of song it’s just a case of mapping notes to the phrases/bars they belong in and mapping those phrases/bars to chains.
After creating the map of notes to bars, I then de-duplicated the phrases to cut down on the number of phrases that needed to be input and stored in chains.
I did this by creating a base64 string of the notes in the phrase and then using a
dict to essentially create a set while also retaining the phrase order in an array.
Printing things out
Of course it’s not useful having the song structure in objects, they need to be converted into a means that makes it simple to input them into LSDJ.
The LSDJ phrase screen shows three/four columns (drums has two notes); note(s), instrument & command and 16 rows, although these rows are in hex so 0-F.
Displaying the drums took a little more effort as the MIDI file would return the pitch but LSDJ uses short code for the different drums such as
bd for bass drum and
chh for close hi-hat.
To show the correct drum I created a map of the different pitches and the drums they related to, additionally drums in LSDJ can play two sounds at once so I extended the phrase view to show both notes.
In order to make it easier to find my place (there’s a lot of phrases) I decided to reproduce the column and row structure in a very basic way, this worked very well as you can clearly see what was meant to go where and what phrase I was in.
If the phrase would have to end early (due to being in 6/8 etc) then I had to ensure the last row contained a
H00 in the command row (this tells LSDJ to jump to the next phrase).
For the chains I did a similar display but only with the row number and phrase.
Putting it all together
After working out most of the kinks in the script I decided to bite the bullet and actually see if everything I programmed would actual result in something resembling the song… and it did!
Aside from the fact that I had no mental map of what phrases contained what notes (as this was being taken care of by the computer) it took just over 2 hours to program the drums and two pulse channel instruments for the cover and when I pressed play for the first time it worked perfectly.
In order to make it easier to offset the phrases and chains when adding multiple channels I ended up building some command line arguments which handled this but as the LSDJ numbers are in hex I had to do some conversion before putting the numbers in.
If I’m perfectly honest I was absolutely amazed it worked first time, I had expected it to fail but my fingers were glad that I didn’t need to spend another 2 hours programming in notes!
I then set to work on recording a separate bass track and solo track as well and recording the drums and guitar tracks using my half speed gameboy, mixed it all together.
The end result of this process can be found below:
The script is by no means perfect and I’ve got a list of improvements I’d love to make:
- Triplets — Certain songs utilise these (especially in solos) and I’d like to automate this as it requires running the commands at double speed
- Chords — It’d be great to take all the notes being played at a certain tick and automate the creation of a table to play the chorded notes
- Multi-channel import — Instead of processing a file at a time instead either process a multi-channel MIDI file or separate files but via a YAML file or something as that will make it easier to use
- A JSON/HTML display — I’d like to turn the script into a web service where the output is JSON that could then be fed into a React display. My aim would be that anyone can upload a MIDI file and get the LSDJ commands they need to put it on their Gameboy
- LSDJ save file generation — There’s a Python library for LSDJ save files which I could use, ultimately saving even more time as I just need to add this to my cartridge and press play
- Publish it — So others can use it!