ClownMDEmu - The Greatest Mega Drive Emulator Ever (Someday)

(Cross-post from my blog)

v0.8
Try it in your web browser: clownmdemu.clownacy.com
Download: https://github.com/Clownacy/clownmdemu-frontend/releases/tag/v0.8.0.1

v0.8 is an update that was burdened by attempting a major change to the codebase. This change was meant to greatly improve the emulator's frontend, allowing for the elimination of numerous hacks within its codebase while making new features possible. Unfortunately, this did not go to plan, and all of the work on it cannot be released today. This change was the migration of the frontend from version 2 of the SDL library to version 3.

This would have been perfect for adding native multi-window support, but deficiencies in the pre-release SDL3 meant that the frontend would also be rendered extremely unstable and buggy. Instead, today's release is a refinement of the emulator based on its prior software stack. While it would be great to announce and show off native multi-window, that feature will have to wait until SDL3 matures. With that out of the way, here are the features that avoided disappointment.

Dark Title Bar on Windows 11
image-4.png


It is about time! Thanks to a tip from Ewan, the frontend now features a little WinAPI hack to set the title bar to the same colour as the Dear ImGui menu bar. This will only work on Windows 11 due to using a recent API.

image-7.png

Now Windows 11 users no longer have to put up with the ebony-and-ivory look that the emulator previously had.

Linux AppImage Build
Linux executables tend not to be anywhere near as universal as Windows executables: their heavy reliance on system libraries and lack of standalone installers make it very difficult to share precompiled software in a form that will work without any hassle on a variety of Linux distributions. Because of this, users either have to compile software from source code, or wait for their distro to provide it as a package. Unfortunately, clownmdemu is not provided by any distro, meaning that compiling from source is the only option. However, this is not a simple process, as it requires recursively checking-out the Git repo and then bootstrapping the build system with CMake - things which beginner programmers are apparently unfamiliar with. All of this combined makes it quite difficult for a Linux user to obtain a useable copy of my emulator.

This is where AppImage comes in - AppImage is a standard for Linux software which bundles all files (executables, libraries, and data) into a single executable, making it very convenient to share, download, and run. Additionally, software is encouraged to be compiled on the oldest still-supported version of Ubuntu LTS, allowing it to run on a huge range of Linux distributions without risk of glibc incompatibilities. With AppImage, I can produce a Linux build that is easy to download and run while also being compatible with the vast majority of current Linux distributions!

Supporting AppImage was not particularly difficult: it merely requires that the emulator's build system be capable of installing the software in a way that adheres to some freedesktop.org standards - particularly providing an icon and basic metadata. Likewise, compiling with an old version of Ubuntu was not very difficult, with it only requiring that I mark some CMake scripts as being compatible with older versions of CMake, and also needing to switch from C++20's format library to its backward-compatible middleware version.

With that done, I am now able to create a clownmdemu AppImage! It is provided alongside the Windows executable in the GitHub releases.

But why stop there? AppImage and its two competing standards - Flatpak and Snap - all provide catalogues for users to conveniently browse and download compatible software. It is one thing to make my software easy to run, but it would be even better to also make it easy to find! So, I have added clownmdemu to appimage.github.io!

Fix for Sprite Data Not Being Cached on Prior Scan-Lines
The ROM-hack 'Gametap Pro Gamer 6' had a couple of splash screens which were not rendering correctly in this emulator:

image-5.png
image-6.png

Hey, that's me!

The cause of this bug was surprisingly difficult to uncover, taking me several days. Eventually, I found that this issue was not the product of a bug, but rather a deliberate design choice: back when I had implemented sprite scan-line caching, I made it so that it would not cache the sprite data of scan-lines which had already been rendered. In hindsight, this makes absolutely no sense. I think this was a misguided optimisation, but, after all these years, I no longer remember why I implemented it in the first place. Regardless, by removing this anti-feature, this bug is now fixed:

image-7.png
image-8.png

I really am the best!

Approximation of the YM2612's 'Ladder Effect'
A feature which purists often mention in relation to the Mega Drive is a curious bug that is present in early models of the console: the 'low-volume distortion' effect which is present in the YM2612 sound synthesiser, but not the YM3438 that is used in later models.

The exact details of how this bug works do not yet appear to be understood, however the general idea of it is that an offset is applied to samples on one half of the sound wave. Exactly how large this offset is is unclear, but I have seen at least one mention of it being approximately 4 decrements of the YM2612's internal 9-bit mixer value.

This bug causes quiet sounds to be louder than normal. Many songs and sounds were designed with this behaviour in mind, causing the absence of this bug to make them sound incorrect. For instance, here is a song from Earthworm Jim 2:
Without the low-volume distortion, the grating whining in the background is absent. According to Tommy Tallarico, this whining is an intended part of the song, rendering its absence a flaw.

Another major example of this is the After Burner II soundtrack, with three songs bearing distinct differences:

YM2612:
YM3438:
  • Red Out has a descending note at the start which is inaudible without the low-volume distortion.
  • Super Stripe has an electric piano-sounding instrument playing some backing notes which are much quieter without the low-volume distortion.
  • After Burner has a lead melody whose notes decay much faster without the low-volume distortion.
These differences were egregious enough that they simply needed to be addressed, and so I have introduced a rough approximation of this bug into clownmdemu. Given its rough nature, and the fact that this bug does not exist (at least to the same extent) in later Mega Drives, an option to disable this is has also been added.

image-8.png

Have it your way!

Support for 'KDEBUG' Debug Messages
It is not often that standards manifest within Mega Drive emulation: there is no standard save state format, no standard layout for modern gamepads, and not even an agreed standard for how to convert Mega Drive colours to modern sRGB RGB888. KDEBUG, however, is an exception:

KDEBUG is an extension to the Mega Drive's API that provides a way for software to log text for the user to view. While useless for players, this is great for developers, in the same way that C's 'printf' function is: it can be used to make the game report things to the developer that are difficult to determine otherwise, such as how many of a pool's objects are allocated, or how many entries are currently populating a linked-list.

This API originated in Gens KMod, which is a modified version of the historical Mega Drive emulator Gens. KMod introduced a vast number of debugging utilities to Gens, with this API being one of them. Many years later, this API was adopted by another emulator, BlastEm. This API would go on to be used by Vladikcomper's debugging framework (which is widely used by Sonic the Hedgehog ROM-hacks) as well as SGDK (which is widely used by Mega Drive homebrew). In all, KDEBUG has become quite ubiquitous.

Indeed, it has become so ubiquitous that I have received multiple requests for KDEBUG support be added to my emulator too.

The API itself is pretty simple: it uses one of the VDP's unused registers as a character stream output, allowing strings to be logged by feeding each character into the register one after the other. After the last character is sent, a null character (literally the byte 0x00) is sent, which the emulator responds to by presenting the complete string to the user. On a real Mega Drive, writing data to this register does nothing whatsoever, making it a harmless no-op. This allows software which uses the KDEBUG API to remain compatible with platforms which do not support it.

As with other developer notifications, KDEBUG messages are recorded to the emulator's Log window. By enabling the 'Log to Console' option, the messages will be sent to 'stderr', allowing them to be viewed from the terminal just like a native PC program.

image.png

An example of Vladikcomper's debugger logging which chunk data is being loaded.

Accurate Motorola 68000 Instruction Durations
CPUs work by processing instructions. These instructions can do things like add two numbers together, perform a multiplication, and so on. Different instruction take a different amount of time to execute... and this is what my emulator has been failing to do for its entire existence.

The Z80 CPU emulator has had proper instruction durations for a long time now, but that was due to the necessity of it: games rely on the Z80's timing to be correct so that audio samples do not play at the wrong speed. However, for the Mega Drive's main CPU, the 68000, this necessity did not exist: many games worked perfectly with every instruction taking 10 cycles, and so my emulator did just that.

This remained the case until I overhauled how Z80 interrupts worked, which exposed an issue: in Sonic 2, with each 68000 instruction taking 10 cycles, the Z80 interrupt would consistently occur right in the middle of a long Z80 bus request operation, causing the interrupt to be lost, which meant that the music would play at an uneven, slow speed.

Rather than mitigate the consequences of this hack by adding another hack, I opted to eliminate the hack by finally implementing proper 68000 instruction durations. This was done by creating a test program which ran the 68000 CPU interpreter in isolation and compared the results to a sprawling series of tests whose data was derived from an extremely accurate microcode-level emulator. This way, I could compare my emulator's behaviour to that of the accurate emulator. This is something that I had already done previously back in late 2021, but I only verified instruction behaviour rather than instruction durations. With this new program, I could verify both!

All instructions now have accurate durations! With this, this music in Sonic 2 no longer plays at a wonky speed, and games should lag (or not lag) more consistently with how they do on a real Mega Drive.

True Multi-Window
Ever since debug menus were first added, they looked like this:

screenshot-debug.png


The menus mostly worked, but they had one glaring limitation:

image-3.png


They were all fake! It was not possible to see a 'fake' window outside of the 'real' window! Yes, you could maximise the emulator, but that meant obscuring all of the other programs that you were using, like your web browser!

The reason that the windows were this way was because they were made with Dear ImGui, which is a library for quickly and easily creating menus. This library works across many different operating systems, rendering its windows inside the program's main window by design, so that it works on video game consoles, which lack a proper windowing system like what a PC has. Unfortunately, this illustrates how Dear ImGui is better-suited for embedding in a video game than for developing a sprawling desktop application.

Despite this, I continued using Dear ImGui because I hoped that its developers would eventually address this shortcoming and allow for the creation of proper windows on platforms that support it. Indeed, such a feature has already been in development for quite some time!

97542423-fe8ffb80-19c6-11eb-9bf5-e26d86364e55.png


There was just one problem: it did not work on Linux. Not only was the X11 support extremely buggy, but it was fundamentally incompatible with Wayland, meaning that it would never support the protocol without first undergoing major modifications. This was a deal-breaker.

For years, I hoped that something would change, but nothing ever did. Even now, Dear ImGui's 'multi-viewport' support is completely unusable on modern Linux.

Finally, I had waited long enough, and decided that, if Dear ImGui was not going to support multiple windows for me, then I was going to do it for myself. This is the result:

image-1.png

It's finally here!

At last, all menus are now proper windows! This was achieved by bypassing Dear ImGui and manually creating windows using the underlying API, SDL2. Unlike Dear ImGui, SDL2 supports creating windows on all major operating systems, including Wayland-powered Linux.

Long ago, I had refactored the frontend so that a window was represented by a class, with which it was easy to create multiple windows. By inheriting from this class, I could create a class which represented a window that used Dear ImGui widgets, and then, by inheriting from that, I could create a class that represented a menu window. In this hierarchy, the main window is a 'window with Dear ImGui', while all other windows are 'menu windows'. Each menu can then inherit from the menu-window class and extend it with a unique interface. By structuring the code this way, all menus share a great deal of code, making it very easy to create new ones. Additionally, by centralising so much code into common classes, I made it easy to apply fundamental modifications to all menus at once! This would be very useful for solving one particular problem...

Unfortunately, not all platforms support using more than a single window. One platform in particular is one that my emulator only recently gained support for - Emscripten. By making my emulator use native windows, I had broken compatibility with platforms that cannot use them! To address this, I made it so that a menu can be either a real window or a fake window. Due to the aforementioned centralisation of the core menu code, this was very easy to do! As a result, the Emscripten version of the emulator will use Dear ImGui windows, while other platforms will use normal windows!

Allowing the menus to be used outside of the main window has been a feature that many users have requested over the years, so I am very happy to finally see this fulfilled!

There is just one problem: these menus are jank. On Windows, Linux, and presumably every other platform, these windows are not considered 'subwindows' of the main window. This means that each one gets a slot on the operating system's task bar, and that un-minimising one window will not cause the rest of them to be un-minimised along with it. These sound like small problems, but they really bring the whole experience down.

image-9.png

This is annoying.

SDL2, despite flaunting its ability to create multiple windows as one of its main improvements over SDL1, has no API to overcome this. There is one function for designating a 'modal' window, but it does not solve this particular problem. Not to mention, that function is only implemented on Linux; it does absolutely nothing on Windows and macOS, and was only introduced because the Linux port of the Unreal Engine's editor needed it. It is things like this which call SDL2's role as a platform-abstraction library into serious question.

Because of this, Dear ImGui windows remain the default for now, until something can be done to make native windows less clunky. For those feeling adventurous, native windows can be enabled by editing the frontend's configuration file, which can be found at '%APPDATA%/clownacy/clownmdemu-frontend/configuration.ini' on Windows and '~/.local/share/clownacy/clownmdemu-frontend/configuration.ini' on Linux. Just change the 'dear-imgui-windows' setting from 'on' to 'off', and you are good to go!

SDL2's successor, SDL3, provides a function for making a window the parent of other windows, which would solve all of these problems. One would think that the obvious thing to do is migrate this project to SDL3 so that it can make use of this feature, and that is exactly what I did! Unfortunately, SDL3 is still a work-in-progress, being far too unstable for regular use. While the day will come that the switch to SDL3 can be made, and the improved native windows can finally be released for everyone to use, that day is not coming any time soon.

Preliminary Migration to SDL3
Background
From the very beginning, this emulator has used the SDL2 library for handling the various differences between operating systems, allowing the emulator to work on any platform with minimal effort. There is one problem with it, however: it is quite old.

SDL2 as we know it began as SDL 1.3 in the late 2000s. Since its inception, it has maintained a rock-stable programming interface (both API and ABI), which allowed it to continue working with all software that had been built upon it, even after many years of releases, features, and heavy refactoring. This interface also had the unfortunate downside of cementing various poor design decisions into SDL2, forcing developers to deal with them for well over a decade.

At long last, SDL's developers have opted to shed these years' worth of flaws by developing SDL3, which boasts a refreshed API. Gone are quirks like functions returning 0 to indicate success while others would do it to indicate failure, the filtering of textures being specified by clunkily altering global state, and high-DPI support on Windows being enabled completely differently to how it is for other platforms.

In addition to a cleaned-up interface, SDL3 boasts new features like a 3D rendering API, file dialogs, and a replacement for the conventional 'main' function that makes software immediately compatible with Emscripten.

None of this is particularly beneficial to my emulator, since it does not need 3D rendering, it already uses its own custom file dialog logic, and it provides special-case code for Emscripten to work around the incompatibility with 'main' functions. However, by discarding this custom code and offloading the work to SDL3, not only would the codebase be simplified and lightened by several hundred lines of code, but improvements made to SDL3's implementation of these features would directly benefit the emulator, whereas it would remain completely unaffected if it continued using its own implementations.

What ended up pushing me over the edge, however, was the ability for a window to declare itself as the child of another window. By doing this, the child window is not allocated an icon on the task bar, and it is presented to the user whenever its parent is. This may seem minute, but it goes a long way towards making a multi-window program not feel like a mess to use.

Wanting to make the emulator's multi-window support the best that it can be, I decided to migrate my emulator from SDL2 to SDL3.

Migration
The process of migrating to SDL3 was very complex. Fortunately, the developers of SDL provide extensive documentation for how to port software to the new library, along with a few scripts to automate certain tasks such as renaming functions.

One of these scripts gave me a great deal of trouble, that being the Coccinelle script. Coccinelle is a program meant for applying patches to source code in a syntax-aware manner, but, on Windows and Arch Linux, it merely crashes when attempting to run the script. It lacks a Flatpak and neither MSYS2 nor Arch Linux provide a package for it, requiring that it be compiled from source, which makes it unclear whether the fault is with the program or merely how it was compiled. The only way that I got the script to run was by installing a recent version of Ubuntu in a virtual machine and installing and running its Coccinelle package from there, which is far more effort than it is worth. I do wish SDL's developers had chosen a more reliable tool.

After applying the Coccinelle script, I was able to run the accompanying Python scripts without any problems. From there, I consulted the migration documentation to address every compiler error that I encountered until the emulator successfully compiled. Unfortunately, a few bugs had sneaked in along the way, due to some API changes not producing compiler errors despite the issues they caused, such as the 'SDL_Event' struct's 'cdevice' ('controller device') member being renamed 'gbutton' ('gamepad device'), and then a new member being added that was also called 'cdevice' (short for 'camera device' instead). This problem could have been avoided by using a more verbose naming scheme.

With the emulator now running atop SDL3, I could replace its custom file dialog and 'main' function logic with usage of SDL3's new APIs. I also considered doing the same to replace the audio mixer, but I am uncertain about whether SDL3's mixer features dynamic rate control like mine does, so I have not made the switch.

Result
As of now, the SDL3 migration is complete. The codebase is benefitted by this in multiple ways: being free of many lines of code that were made redundant by SDL3, having clearer code due to targetting a better API, and being able to make use of features that did not exist in SDL2. With this, the emulator is prepared for the future!

...Unfortunately, it is not prepared for the present: SDL3 is still pre-release software, meaning that it is lacking in polish and stability. A glance at SDL's issue tracker will show many flaws in its current implementation. The worst problem occurs when closing a window. On Windows 11, this causes the desktop to behave erratically, as if the compositor or GPU driver had crashed, while on Linux and Windows XP, the emulator itself crashes. Strangely, this only seems to occur with the software and Vulkan renderers; OpenGL and DirectX appear to work just fine.

Because of all of this, the SDL3 migration was moved to its own dedicated branch. The main version of the emulator will remain with SDL2 until things improve. Those who are interested in trying the SDL3 port can find its source code here.

Closing
I think this is the first time in the project's history that I have implemented features which were either completely reverted or locked behind a hidden setting. Both native window support and the SDL3 migration held this update back for months, so it is a tremendous shame to not see all of that effort pay-off. This has me wondering if it would be worth abandoning SDL in favour of another library, such as GLFW. Unfortunately, SDL is so deep-rooted into the codebase that removing it would involve rewriting a great amount of code. While it is possible, it would not be easy. Still, refactoring is one of my favourite programming tasks, so maybe I will take the plunge. Only time will tell.
 
Last edited:
(Read this on the blog if the images look wrong.)

Try it in your web browser: clownmdemu.clownacy.com
Download: Standalone, libretro

I was expecting the first update after v1.0 to be small, but it ballooned into something massive; compatibility has been greatly improved in various areas, and many features have been emulated for the first time!

VDP Behaviour Accuracy
The Mega Drive's Video Display Processor has many strange, undocumented, and unintuitive behaviours, some of which are so obscure that they are unlikely to be relied upon by any games. Because apparently no games require these behaviours, it is difficult to know that they exist in the first place, but this is where homebrew can be very useful!

Once again, Nemesis's suite of VDP tests proves to be invaluable, as it thoroughly demonstrates many of these behaviours, allowing developers to easily tell if their emulators correctly recreate them! Additionally, the source code to this homebrew is publicly available, allowing developers such as myself to study the code to see exactly how the homebrew is invoking these quirks.

Unfortunately, the source code is often light on documentation, with the absolute worst case being that it does not explain that DMA Fill operations to CRAM and VSRAM do not fill with the usual DMA Fill value, but rather an arbitrary value from the VDP's FIFO. I was only able to figure this particular quirk out by conveniently remembering some posts on the SpriteMind forum that I had read long ago. Without that, I may never have understood how the homebrew was doing what it did.

Regardless, it is still an incredibly useful tool, without which I would not have been able to implement so many of the VDP's edge-cases!

After several hours of trial-and-error and analysing the source code, I was able to make my emulator pass many more of its tests, going from 49/122 to 108/122!

image-30.png
image-25.png


CD-Controller Emulation Accuracy
There are multiple ways for a Mega CD game to load data from its CD: reading in bulk by using a BIOS call, streaming a word at a time by reading a register, and reading in bulk by using a DMA transfer. Until now, this emulator only supported the first option. Neither Sonic CD nor Sonic Megamix use the other options, so, to test this feature, I created some homebrew.

Not wanting to write homebrew from scratch in assembly, I extended my Mega Drive C++ toolchain to support creating Mega CD homebrew. In particular, it supports creating "Initialisation Program" ("IP"), and "System Program" ("SP") payloads, which are automatically loaded by the BIOS for execution by the MAIN-CPU and SUB-CPU, respectively. These payloads differ from typical cartridge software in their memory layout and bootstrapping code: IP and SP payloads need not perform hardware initialisation, as this is handled by the BIOS, while SP payloads additionally need a small header which declares a series of callbacks for the BIOS to invoke. With this done, I had a small homebrew Mega CD "game" that loaded and displayed its first CD sector.

image.png


At first, this homebrew was tested by running it in Genesis Plus GX. What is notable about this emulator is that it performs low-level emulation (LLE) of the Mega CD's BIOS, making it a reliable way of verifying the BIOS's behaviour, unlike my emulator which skips emulating the BIOS completely by leveraging high-level emulation (HLE). I would have instead verified this homebrew by using a real Mega CD, but without an optical drive emulator (ODE), I would be forced to burn numerous CD-Rs to use with the console, which would be a waste. Alternatively, I could have converted the homebrew to "Mode 1", which would allow it to run from a cartridge instead of a CD while still being able to make use of the Mega CD hardware, but I wanted to exercise my C++ toolchain's ability to produce 'proper' Mega CD software. With the homebrew working in Genesis Plus GX, I then began implementing the missing CD data transfer modes in my emulator.

Since first adding Mega CD support to my emulator, I have learned more about how the console's BIOS calls and CDC unit work. Much of this information came from Devon, who contributed much to the Mega CD emulation, such as its stub BIOS. This information revealed that much, if not all, of the CDC and BIOS call emulation was wrong, so I discarded the existing code and reimplemented it all from scratch, taking into account quirks such as the 128 kbit CD-ROM FIFO and how the 'ROM' BIOS calls were merely wrappers around the 'CDC' BIOS calls.

After a round of fixing various bugs in this new implementation, the homebrew was able to run correctly! However, Sonic CD failed to boot. Using my emulator's built-in debuggers, as well as Visual Studio's debugger, I located the game's CD-reading code and examined how it was executing, as well as how the emulator responded to it. By doing this, I was able to identify and fix more bugs in the CDC emulation code, allowing the game to boot.

With Sonic CD working, my focus shifted to Sonic Megamix; the Mega CD version of this famous ROM-hack has never booted in my emulator before, but, with the experience of reverse-engineering Sonic CD's CD-reading code fresh in my mind, I was ready to dive into Megamix's internals to finally unravel this mystery.

My examination uncovered one more bug (which had to do with data in the CD-ROM FIFO being overwritten before it could be read) but it also revealed some edge-cases in the CDC/BIOS's behaviour. The reason for this was that Megamix's CD-reading appears to suffer from some bugs: rather than issuing BIOS calls until they succeed, it does so until they fail. This results in the BIOS calls 'CDCREAD' and 'CDCTRN' being issued twice. 'CDCREAD' is responsible for preparing the sector at the end of the CD-ROM FIFO for transfer, and 'CDCTRN' is responsible for performing the transfer. This raises the questions of what should happen if the same sector is prepared for transfer twice, and what should happen when the same sector is transferred twice. Should the second call to 'CDCREAD' re-prepare the sector for transfer and signal success, or should it detect that a sector is already prepared and signal failure? Should the second call to 'CDCTRN' transfer a second copy of the sector and signal success, or should it detect that the sector has already been transferred and signal failure? My emulator was doing the former in both cases, while Megamix showed that it should have been doing the latter.

With these inaccuracies addressed...

image-32.png
image-33.png


...Sonic Megamix booted, at long last!

But that was not all: the tech demo 'Sonic for Mega CD', as well as the leaked 'v5.0a' version of Sonic Megamix also booted!

image-35.png
image-34.png


Improved CD Audio Track Compatibility
This emulator is not currently able to play Mega CD games directly from their original discs; instead, users are expected to extract the contents of each of their discs to a file, which the emulator can use. This is a common practice in emulation, with such files being termed 'rips', 'dumps', 'copies', and 'backups'. Over the years, many different formats for Mega CD rips have been devised, which is a problem for emulators, as they need to support an increasingly large number of formats to maximise compatibility with users' libraries of games.

This problem extends to audio formats as well, as there exist Mega CD game formats which encode the audio tracks in Ogg Vorbis, MP3, and WAV. This is even further complicated by the fact that these audio formats can contain audio that differs wildly from that of an actual CD; in particular, audio can have more or fewer than 2 channels, and use a sample rate that is not 44100Hz.

Previously, I had assumed that all Mega CD game files would use 44.1kHz stereo audio, and so my emulator supported only playing audio files that met that standard. After all, real CDs can only ever contain audio in that format, so one could reasonably expect all rips of a CD to have its audio in the same format; to increase the channel count or sample rate would be to needlessly waste space by adding redundant data, and reducing the channel count or sample rate would lower the quality of the audio by discarding useful data.

However, this logic fails to account for homebrew and ROM-hacks: instead of being created by extracting data from a CD, homebrew Mega CD audio files can be sourced from live audio recordings, exported from music software, downloaded from the internet, or even extracted from other games. This means that these audio files can use practically any sample rate and channel count, completely shattering the assumption that they would be 44.1kHz stereo.

I encountered this issue when trying to play Sonic Winter Adventures, which is a ROM-hack of Sonic 1 that was made by Vladikcomper back in 2013. This game comes with a soundtrack of MP3 files, some of which are 44.1kHz, but others are 32kHz. The result of this was that some songs would not play in the emulator, leaving the game without music on its title screen and upon completion of a level.

To resolve this problem, I added a resampler (clownresampler) to the emulator's disc-rip-reading library (clowncd). With this, I could make the library convert audio to the expected 44.1kHz sample rate at runtime. I also added some logic to upsample mono audio to stereo. By doing this, my emulator was granted compatibility with audio that uses alternate sample rates as well as audio that used only 1 channel.

CD-DA Fader Emulation
The CD-DA music emulation is currently quite basic, lacking features such as fast-forwarding and reverse playback. However, one feature that is no longer missing is the fader, allowing for CD-DA volume control and fading. Curiously, Sonic CD always keeps its CD-DA volume slightly below maximum, so this change will cause Sonic CD to have quieter music. This feature is also used by Sonic Winter Adventures, which fades out its music before boss battles.

Sega's official documentation for this feature is quite lacking, not explaining exactly how the volume values affect the loudness of the audio, nor how the "change speed" value relates to the volume. There is also an inconsistency between the official documentation and official sample code, where the former claims the fader updates 75 times per second while the latter claims it to be 60 times per second. To solve these problems, I examined Sega's BIOS; a disassembly of the Mega CD BIOS can be found on GitHub, revealing the internals of how fading is done. In reality, it is quite simple: samples are multiplied by the volume and divided by 0x400, and the "change speed" is a value that is added to the volume every time the fader updates. The fader is updated every time the CDD interrupt occurs, which is 75 times per second.

With these details figured out, emulating the fader took only half an hour or so. As far as emulating features goes, this was one of the easiest. As of this change, I believe that every feature which Sonic Winter Adventures uses is now emulated.

'Coordinate Conversion' Emulation
The Mega CD provides some graphical capabilities, one being "coordinate conversion", which creates tiles by sampling other tiles. This is done by taking a pair of coordinates, sampling the pixel at those coordinates, adding a delta to the coordinates, and repeating. This is similar to how texture sampling works in shaders.

At the lowest level, this effect is achieved with "stamps", which are in the same format as sprites and can be 16x16 or 32x32. These stamps are arranged into a grid, forming the "stamp map".

Here are the stamps that are used by Sonic CD's special stages:

image-1.png


Here also is a section of its stamp map:

image-2.png


Coordinate conversion works by rasterising an image, forming lines from pixels, and then forming a whole image from those lines. The lines are formed from left to right, and the image is formed from top to bottom. Each line is ascribed metadata by the "trace vector", which details the line's starting sample coordinates and delta; the leftmost pixel of the line is assigned the pixel of the stamp map that is at the location specified by the starting coordinates. The coordinates are then advanced by the delta, and the next pixel is assigned the pixel of the stamp map at the new coordinates. This is repeated until the line is complete, at which point, the next starting coordinates and delta are fetched from the trace vector and a new line is produced. This is repeated until all lines are drawn, at which point the process ends.

Researching this feature was difficult, due to the official documentation for it being very fragmented: the Mega CD Hardware Manual, Mega CD Outline Manual, and Mega CD Development Manual each describe parts of the coordinate conversion process, some containing unique information, and others containing a reworded version of documentation that can be found in another manual.

Fortunately, Devon provides a thorough and concise explanation of this feature along with some sample code on GitHub, which was enough for me to mostly understand it.

I still had some uncertainties about the feature, however, such as what should happen when the 'image buffer V-cell height' and 'image buffer dot height' are mismatched. To answer these questions, I expanded my C++ Mega CD homebrew to showcase the output of the coordinate conversion when supplied with various parameters. This answered the last of my questions, allowing me to finally move onto implementing the feature.

In the process of implementing coordinate conversion, I added the two debug visualisers that are seen above. The size of the stamp map caused the emulator to lag when its visualiser was enabled, which led me to overhaul the visualisers to use hardware-accelerated rendering, greatly improving their performance.

In comparison to overhauling the visualisers, emulating the coordinate conversion was easy, due to the feature being thoroughly examined before I began implementing it, allowing me to plan the implementation in advance. Within a couple of hours, the first prototype was complete, allowing Sonic CD's title screen, special stages, and D.A. Garden to appear correctly:

image-3.png

Used for the clouds.

image-4.png

Used for the floor.

image-7.png

Used for the planet.

With this, the only missing features which are relied on by Sonic CD are BuRAM (save data), CDC transfer durations (FMV audio), and possibly audio track lead-in (music timing).

New Logo
I was visiting a retail park near where I live when I came across a Dunkin'. Something about its sign struck me as familiar:

image-9.png
icon-256.png


Oh dear.

ClownMDEmu's logo was designed to be as minimalist as possible. Unfortunately, the problem with minimalism is that designs tend to look similar. In this case, by sheer coincidence, Dunkin' had also come up with the idea of reducing a name to four letters, arranging them into a square, and using a font that is made of circles.

Since I am not interested in getting involved in a trademark dispute (and a million 'haha Dunkin' logo funny' jokes), I have switched the font to a rounded version of the 8x8 font that I made for the emulator's custom Mega CD boot ROM.

icon-256-1.png
icon-256-2.png


I would have kept the pixelated font, but I find that the hard corners clash horribly with the rounded design of the rest of the logo.

YM2612 LFO Emulation
The final major unimplemented feature of the the Mega Drive's main sound chip is the low frequency oscillator (common referred to as 'the LFO'). This is a fairly-simple waveform that modulates an operator's amplitude (volume) and phase (pitch), allowing for quick-and-easy tremolo and vibrato. Very few of the Mega Drive games that I own make use of this feature, and support for it was removed from the modified SMPS sound drivers that are used by many Sonic games. SMPS provides its own means of achieving vibrato and tremolo which are much more powerful than that offered by the LFO (larger modulation range and custom envelope shaping), which could explain why support for the redundant feature was removed.

While a lack of games which require LFO was one reason that it took so long for the feature to be emulated, the other reason was that thorough documentation of it is practically non-existent: the official Yamaha documentation of the YM2612 only gives a high-level overview, which is of little help in understanding how to emulate it, and community documentation is equally lacking, with Plutiedev simply paraphrasing the Yamaha documentation, the Mega Drive wiki containing barely any documentation at all, and even the incredible SpritesMind YM2612 thread lacking a proper write-up, only debating a handful of details of its functionality with few concrete answers. Tragically, Nemesis had planned on providing a thorough write-up of the feature, in the same vein as what he had done previously with the envelope and phase generators, but life had gotten in the way.

This lack of documentation has proven to be a problem for other emulators as well, as Eke-Eke, the maintainer of the Genesis Plus GX emulator, can be seen multiple times in the SpritesMind thread asking for any details about the internals of LFO, as many games exhibit inaccuracies when the feature's behaviour is not recreated exactly.

Despite all of these obstacles, there was one breakthrough that finally made accurate LFO emulation possible: the Nuked OPN2 project, which is a transcription of the YM3438's circuitry to the C programming language (the YM3438 is the CMOS version of the YM2612). With this, the exact behaviour of the LFO is documented in complete detail (barring any potential transcription errors).

I was not content with merely copying code directly from Nuked OPN2, so instead I set about implementing LFO it in a way that is optimised and natural to read while still being functionally-identical. This did unfortunately reach a limit, as the YM2612's unconventional logic leads to rounding errors which require equally-unconventional code to recreate. This is especially noticeable with the logic for phase generator modulation: the YM2612 implements this using a pseudo-multiplication made of shifts and an addition, but it discards many bits in the process that a true multiplication would not, producing values that are off by 1. As a result, the code for this is less sightly than I would like it to be:

C:
#if 1
    /* This goofy thing implements a fixed-point multiplication using only shifts and an addition.
       Unfortunately, this particular method is required in order to recreate the rounding errors of a real YM2612,
       which prevents me from replacing it with a real multiplication without sacrificing accuracy. */
    static const cc_u8l lfo_shift_lookup[8][8][2] = {
        {{7, 7}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, {7, 7}, {7, 7}},
        {{7, 7}, {7, 7}, {7, 7}, {7, 7}, {7, 2}, {7, 2}, {7, 2}, {7, 2}},
        {{7, 7}, {7, 7}, {7, 7}, {7, 2}, {7, 2}, {7, 2}, {1, 7}, {1, 7}},
        {{7, 7}, {7, 7}, {7, 2}, {7, 2}, {1, 7}, {1, 7}, {1, 2}, {1, 2}},
        {{7, 7}, {7, 7}, {7, 2}, {1, 7}, {1, 7}, {1, 7}, {1, 2}, {0, 7}},
        {{7, 7}, {7, 7}, {1, 7}, {1, 2}, {0, 7}, {0, 7}, {0, 2}, {0, 1}},
        {{7, 7}, {7, 7}, {1, 7}, {1, 2}, {0, 7}, {0, 7}, {0, 2}, {0, 1}},
        {{7, 7}, {7, 7}, {1, 7}, {1, 2}, {0, 7}, {0, 7}, {0, 2}, {0, 1}}
    };

    const cc_u16f f_number_upper_nybbles = f_number >> 4;
    const cc_u8l* const shifts = lfo_shift_lookup[phase_modulation_sensitivity][phase_modulation_absolute_quadrant];
    step = (f_number_upper_nybbles >> shifts[0]) + (f_number_upper_nybbles >> shifts[1]);

    if (phase_modulation_sensitivity > 5)
        step <<= phase_modulation_sensitivity - 5;

    step >>= 2;
#else
    /* This is what the above code is an approximation of. */
    static const cc_u8l lfo_lookup[8][8] = {
        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
        {0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
        {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02},
        {0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03},
        {0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x03, 0x04},
        {0x00, 0x00, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06},
        {0x00, 0x00, 0x04, 0x06, 0x08, 0x08, 0x0A, 0x0C},
        {0x00, 0x00, 0x08, 0x0C, 0x10, 0x10, 0x14, 0x18}
    };

    step = f_number * lfo_lookup[phase_modulation_sensitivity][phase_modulation_absolute_quadrant] / 0x100;
#endif

With all of that done, games are now able to make use of this underutilised feature!


FM3 Multi-Frequency Operator Order
A bug that was reported on GitHub was that Another World had some broken sounds. I had recently picked up a copy of this game to test another issue, so I was able to experience this problem for myself. True to the reporter's word, some sounds were completely incorrect, notably the splashing sound that would occur at the start of the game as the protagonist climbs out of a pool. With my emulator's debugger, I could tell that the broken sound made use of the YM2612's per-operator frequency feature, though I had no way of knowing if that had anything to do with the bug.

To figure out exactly which part of the sound was causing the bug, I recreated the YM2612's configuration in some C++ homebrew, and gradually disabled features until the resulting sound was identical between my emulator and Nuked OPN2. Eventually, I found that the problem stemmed from the frequencies of operators 1 and 2 being swapped, and yet, looking at the source code, I could not see how the operators could possibly be getting swapped. That changed, however, upon looking at documentation:

image.png


A9, AA, A8, and then A2...? The ordering is all wrong! Operators 2 and 3 being swapped is normal, but operator 1 coming after those two and operator 4 being before them all? None of that makes any sense! Doubting the documentation, I checked Nuked OPN2 only to be further persuaded - the frequency registers really are in a garbled order!

C:
case 1: /* OP1 */
   chip->pg_fnum = chip->fnum_3ch[1];
   chip->pg_block = chip->block_3ch[1];
   chip->pg_kcode = chip->kcode_3ch[1];
   break;
case 7: /* OP3 */
   chip->pg_fnum = chip->fnum_3ch[0];
   chip->pg_block = chip->block_3ch[0];
   chip->pg_kcode = chip->kcode_3ch[0];
   break;
case 13: /* OP2 */
   chip->pg_fnum = chip->fnum_3ch[2];
   chip->pg_block = chip->block_3ch[2];
   chip->pg_kcode = chip->kcode_3ch[2];
   break;
case 19: /* OP4 */
default:
   chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6];
   chip->pg_block = chip->block[(chip->channel + 1) % 6];
   chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6];
   break;

Accounting for this in my emulator finally got Another World's sounds to play correctly.

Lock-On Technology Save Data
Immediately after releasing v1.0, which prominently featured support for creating persistent save data, it was reported that Sonic 3 & Knuckles did not save. Being such a major title for the Mega Drive, this defect was a high priority to fix.

The cause of this problem is that ClownMDEmu only creates save data if the game's ROM header declares that it uses save data. While the emulator could just always create save data by default, that would break compatibility with games which allegedly have anti-piracy measures that are triggered by the presence of save-data functionality; the cartridges for these games lack save data hardware, so the anti-piracy measures are not tripped when using them.

Sonic 3 & Knuckles uses the ROM header of Sonic & Knuckles, which does not declare save data, however it does appear to contain data which indicates where the Sonic 3 ROM header is, which does declare save data. By using this data to make the emulator search both headers, the emulator finds the save data metadata and enables the game to correctly save its data.

Migration to SDL3
It finally happened; SDL3 has been officially released. When support for SDL3 was first added to this emulator's frontend, the library was immature and riddled with bugs. Since then, the situation has greatly improved, with the only bug that I could find apparently actually being a bug in Nvidia’s Linux driver.

Because of this, I am finally confident in moving the frontend over to SDL3 permanently! With this move comes future-proofing, the elimination of platform-specific code, and...

image-1.png


...native multi-window! At last!

Closing
Yet another giant update to add to the list! I am glad that v0.4 was not the only time that I got to release so many improvements in a single update! Like with that update, however, I am feeling quite burnt-out, so do not expect v1.2 to arrive for a while!

My mind has been wandering lately to adding support for the Master System and Game Gear. I am not sure what I would be getting myself into by trying to do so, but unlocking another huge library of games for my emulator to run does have a lot of appeal to me...
 
Last edited:
Back
Top