The Journey To MODPlay

from Michal's blog

← Go back to the homepage

Software development | Published 2022-09-11 19:23:14 UTC

MODPlay web screenshot

MODPlay is an easy-to-use MOD file player library written in C which is near-100% compatible with the original ProTracker (except for a few weird effects which behave quirky in most trackers anyway). While only being a player (compared to a fully featured editor), it was still a journey to get it to a state which I am now happy with.

And I built a neat little retro-themed web-based MOD player app using this library. To try it out, just click on the screenshot above.

What is it exactly?

It is a .mod file player. MOD files are a type of music format, which contains samples and a list of notes called "patterns" (along with "effects": changing the amplitude/pitch of a note, changing the playback speed etc.) - it sort-of resembles MIDI, but you can create your own musical instruments. It is an extremely primitive sound format from the 80s and it can only play up to 4 notes at a time (due to the fact that it was originally meant to be used on the Commodore Amiga computers, which had a built-in 4-channel hardware sampler), but that's why I was compelled that I would be able to make a player for it. The difficult part, though, was to make a player that would not sound like utter crap.

A long time ago, when I was bored one day, I created a simple program in C called MODPlay - it was an extremely barebones MOD player, and I feel that there were countless similar projects (with even identical names) from different authors, who were intending to create a MOD player as well. The problem was that it was simply terrible in practice - it did not support most of the effects (and those which were supported were often badly implemented, which ended in ear-rape at best, program crash at worst), it was rendering audio in mono (.mod files expect that the 1st and 4th channel are played on the left speaker and the 2nd and 3rd one on the right speaker - a hardware limitation of the Commodore Amiga, but some tunes make clever use of this limitation for interesting stereo effects), sometimes it just straight-up skipped notes (as the pattern processing routine (the thing that reads the commands and sends instructions to the sampler on what to play) and the sampler ran on 2 independent threads), it did not perform any interpolation on the samples (so the output sounded a bit "crunchy"), the played notes were at times completely out of tune etc. It was just pure misery, and I eventually got tired of working on it, so I stopped.

In April 2022, it appears that I was seriously bored (I remember that I should have been doing homework, or something like that) and I turned that incredible steaming pile of poo into something actually usable. The first thing I did was that I split the program into 2 parts: an easy-to-use library (seriously, it cannot get any simpler - it has an init function, a function for rendering audio and a function for jumping around the tune) and a player program, which simply links with this library. This way, MODPlay can actually be useful, as devs can implement this library easily into their own productions. The next thing that I fixed was that I consolidated the 2 separate threads into a single one, new notes are now being processed if the rendering function runs out of samples, so that race condition simply never occurs and all notes are properly played.

As of writing this post, I have implemented most of the required effects correctly, and those which are left unimplemented are either non-standard (i.e. some smartarse thought that it would be a good idea to introduce things that are incompatible with everything - I am mostly talking about the panning effect, which the original Commodore Amiga physically cannot do), or they are so outlandish, that nobody is even sure on what they really do.

Of course, this new player library generates audio in stereo (but in a pleasant way, the channels are mixed so that headphone users do not get a headache from listening) and it performs linear interpolation in between the samples (if a sampler decides, that it needs to generate a value between 2 samples, it just simply calculates it), so that the output is not "crunchy" anymore (which is actually better than the Commodore Amiga, as those computers used to generate crunchy sound, but then they added an analog filter on the audio outputs, which gets rid of high-frequency noise (i.e. the crunchiness), but then the audio sounded muffled).

The result of my work is a .mod player library (and an example program) written in C, of which I am quite proud, I wouldn't be afraid comparing the audio output quality to "giants" in the .mod scene (mainly OpenMPT - but keep in mind that that program also supports formats other than .mod, and it is even an editor, not just a simple player). It is still called MODPlay (because of my lack of creativity) and it is of course publicly available on GitHub for everyone to explore, try out and learn from.

It is so lightweight, that I managed to run it on a Raspberry Pi Pico (with a 133 MHz CPU!):

The blinking indicates the CPU usage (when the LED is on, it means that it is generating new audio). I gave it a gigantic 64 kB audio buffer, so that the blinking is more apparent on camera and measurable (by counting the number of frames in a video editor, I'd estimate ~30 % usage of one core). Source code for the Pico is available here.

One day, I thought it would be fun to compile this library using Emscripten into WebAssembly, so that it could run in a web browser. The library managed to compile without any modification to its code, the compiler just took standard C (without any extra Emscripten-specific #includes) and it spat out a .js file, which contained the compiled binary code in Base64 and some supporting code, so that the blob could actually load and run. And of course, it needed some sort of user interface, and I found it quite hilarious to pair my library with an almost-perfect copy of Windows 3.11's UI style (made by taking screenshots in DOSBox and measuring different windows elements in pixels) including its fonts, which I extracted straight out of my Windows install (see assets.js in the Sources section of Developer Tools).

To this program, you can either upload your own file (File → Upload file...), or you can choose from my fine selection which I have prepared for you (you can find them under File → Select from library...). Upon selecting a valid file, it immediately starts playing. You will be greeted by dancing colourful bar graphs (which react to each channel's intensity), on their left will be constantly updating channel info (frequency, volume, sample number) and in the bottom half of the window, you will see the actual song's patterns fly by. During playback, you may alter the song in different ways by exploring the Effects submenu.

But why?

Why not? And I wanted to properly try out WebAssembly (or rather a compiler, which can spit out WebAssembly, because I am not that insane) to actually code in assembly).

If you made it this far, first of all, thank you for following my journey (albeit in this quite short article). Second of all, please give this library a try if you are a developer. I'm sure you'll love working with it and incorporating it into your future productions! The source code repository contains a short example which should get you started.

That's it for today! See you!

In order to post a comment or reply to one, please log in or create a new account:

1 comment total, 1 shown | Page 1

Mik | Posted on 2022-09-20 18:44:43 UTC

Hi Michal, nice projects. I'm using MODPlay for my own SDL2 demo with success. It is easy to use.
I experienced some very short stops and halts with the modfile "global trash 3 V2", -filesize 204728 bytes- from Jesper Kyd but only in the WEB-Modplayer (using firefox esr, 64 bit). In my SDL2 demo with MODPlay there are no stops.
Best regards from germany

1 reply to this comment

1 comment total, 1 shown | Page 1 | Go back to the top