Skip to content
Colin Wren
Twitter

Building a MIDI to LSDJ Web App in Flask & React

Python, JavaScript, Music, Software Development3 min read

blast beat in lsdj
Blast beats, now on the big screen

Continuing on from the work I’d done turning a MIDI file into LSDJ commands I set myself a goal to fix some of the issues I had with printing the entities to the CLI.

My main bugbear with the CLI was the fact that it was hard to keep track of which phrase/chain you were on and if there were many phrases you could easily make mistakes due to how clunky things looked.

Another restriction I found with the CLI approach was the fact that it required someone to download Python and the package to run it.

In order to allow the tool to be used by more people and make it easier to visualise the output of the tool I decided to build a web app to handle a MIDI file upload and return a series of screens representing the phrases, chains and song that make up the LDSJ version of that song.

Serialising LSDJ

In order to display the LSDJ entities in a web app I first needed to refactor the existing CLI so that it could provide a serialised version of the LSDJ structure I used to create the CLI output.

I started by breaking out the original script into multiple modules that would allow me to have a common MIDI to LSDJ translation ‘core’ and then pass that data structure into different ‘presentation’ layers.

One of these presentation layers would be the existing CLI and the other would be a dict that represented all the data for the song that could then be serialised into JSON for a consuming system or app to use.

To help with the design of the JSON format and to allow others to understand how to consume it I created an OpenAPI3 document that details the endpoints and the data structure they’ll return.

1{
2 "chains": [
3 [0, 1]
4 ],
5 "phrases": {
6 "0": [{
7 "command": "",
8 "notes": ["G#3", "A_4"]
9 }, {
10 "command": "",
11 "notes": ["G#3"]
12 }, {
13 "command": "",
14 "notes": ["G#3"]
15 }, {
16 "command": "",
17 "notes": ["G#3"]
18 }, {
19 "command": "",
20 "notes": ["G#3"]
21 }, {
22 "command": "",
23 "notes": ["G#3"]
24 }, {
25 "command": "",
26 "notes": ["D_3", "A_4"]
27 }, {
28 "command": "",
29 "notes": ["G#3"]
30 }, {
31 "command": "",
32 "notes": ["G#3", "A_4"]
33 }, {
34 "command": "",
35 "notes": ["G#3"]
36 }, {
37 "command": "",
38 "notes": ["G#3", "A_4"]
39 }, {
40 "command": "",
41 "notes": ["G#3"]
42 }, {
43 "command": "H00",
44 "notes": []
45 }],
46 "1": [{
47 "command": "",
48 "notes": ["C_3", "D_3"]
49 }, {
50 "command": "",
51 "notes": ["C_3", "G#3"]
52 }, {
53 "command": "",
54 "notes": ["C_3", "D_3"]
55 }, {
56 "command": "",
57 "notes": ["C_3", "G#3"]
58 }, {
59 "command": "",
60 "notes": ["C_3", "D_3"]
61 }, {
62 "command": "",
63 "notes": ["C_3", "G#3"]
64 }, {
65 "command": "",
66 "notes": ["C_3", "D_3"]
67 }, {
68 "command": "",
69 "notes": ["C_3", "G#3"]
70 }, {
71 "command": "",
72 "notes": ["C_3", "D_3"]
73 }, {
74 "command": "",
75 "notes": []
76 }, {
77 "command": "",
78 "notes": []
79 }, {
80 "command": "",
81 "notes": []
82 }, {
83 "command": "H00",
84 "notes": []
85 }]
86 },
87 "song": {
88 "noi": [],
89 "pu1": [],
90 "pu2": [],
91 "wav": [0, 1]
92 },
93 "tempo": 170
94}
An example of the output from the MIDI to LSDJ API. This is the drums for Culinary Hypersensitivity by Necrophagist

I then implemented the API itself using Flask, as it’s a lightweight Python based server and I wrote the original script in Python.

Eventually I’ll add the ability to save the LSDJ JSON into a document store such as CouchDB, at which point I may just have the user access the document as a read-only user.

Recreating the LSDJ UI in React

I’ve been using React for a couple of years now and it’s my favourite front-end framework for building web apps, but I still hadn’t used `create-react-app` to build a new app so I saw this project as an excuse to do so.

The project structure provided by `create-react-app` makes it really easy to get started with React and I’m kicking myself for not using it before (I tended to use existing projects to form my structure).

One of the niceties that `create-react-app` grants you is the ability to import your stylesheets into your component files and then have a compiled version of the style sheet created when you run the final product.

In order to recreate the LSDJ UI I decided to use `rem` measurements as the default font-size of 16 pixels lends itself really well to the Gameboy’s screen dimensions (160px x 144px or 10rem by 9rem) but I doubled this to make things easier to read).

Using `rem` also meant that I could have the UI scale with the user’s browser (say for instance they used Chrome’s zoom functionality) so the site looked good on both desktop and mobile.

scaling the UI
Enhance!

Once I had the UI dimensions blocked out I then had to make the app’s font look like the LSDJ one.

LSDJ supports many fonts but I decided to use the default font as that’s what most people would be familiar with but I couldn’t find a .ttf of it anywhere, I eventually settled on using a C64 font as it was close enough and perfectly square which made it easier to create blocks with.

After creating the Phrase, Chain and Song screens I then hooked them up to the redux state to feed the data into them which worked as intended, I then laid the different entities in a grid layout for easy printing.

part 3

Upload, Song, Chain and Phrase screens

Demo

demo of the finished app
A demo of the web app that I built

You can find a demo version of the site at https://midi-to-lsdj-demo.herokuapp.com/. There are a few caveats to the site, mostly due to the Python side of things:

  • Only the first channel in the MIDI file will be processed and that channel will be assigned to PU1 if it’s found to be monophonic
  • If the name of the file has the word drum in then it’ll be assumed it’s a drum track and it’ll be polyphonic wave based drums
  • The note resolution is currently capped to semi-quavers (16th notes)
  • Only Tempo changes and non-common time signatures are handled currently
  • The song must contain no more than 255 phrases
  • The song isn’t stored, so you’ll need to re-upload

I’ve provided some demo files to upload to test out the app if you don’t have any MIDI files to hand, these are the ones I used to create my Culinary Hypersensitivity cover.

Next steps

  • Allow the user to print out the song as a ‘songbook’
  • Make the MIDI processing better so user can upload multiple channels and this is returned in the JSON response
  • Add abilities to translate chords, grooves etc from the MIDI into LSDJ setup
  • Make the different entities interactive so the user can jump from the phrase definition in chain to that exact phrase