An Epic of Silicon, Bits, and Giving Up Too Early
How I Added the YM2151 Chip to an Existing Orchestra
By Ivan Svarkovsky | July 2026
Engineering Practice / Retro Electronics
There's something sacred about emulating classic sound hardware. Every FM instrument, every envelope decay algorithm carries the "genetic code" of the original silicon.
The Yamaha YM2151 emulation written by J. Burczynski is the gold standard. His code, created over twenty years ago for MAME, still meticulously reproduces every step of the phase generator and every decibel of attenuation of the real chip [1].
In my port of the fMSX emulator to the modern ESP32-S3 microcontroller, a whole zoo of sound hardware was already up and running. I had managed to make this orchestra play in harmony:
- AY-3-8910 / YM2149 — classic PSG
- YM2413 (OPLL) — MSX-MUSIC / FM-PAC
- K051649 (SCC) — Konami SCC
- K052539 — Konami SCC+
I wanted to add the SFG-05 module (based on the YM2151 chip) to this list. And that's when I ran headfirst into the harsh reality of embedded systems.
Act I. The YM2151 Diet: Fighting for Every Cycle and Byte
Code from the desktop PC era was written under the assumption that RAM was practically free. The original driver unapologetically consumed ~68 KB of resources (38 KB for the chip structure and another ~30 KB for static waveform tables).
On the ESP32-S3, such extravagance is unacceptable. The external Flash memory is connected via a slow SPI bus. Every access to it during per-sample FM synthesis (at 16–32 kHz) causes Cache Thrashing, instantly destroying the emulation's stability [2]. And copying 68 KB of tables into the scarce internal DRAM would starve the FreeRTOS kernel and VGA framebuffers of resources [3].
I had to completely rework the driver's physical infrastructure, moving computations into the fast registers of the Xtensa LX7 processor.
1. Mathematical Compression (16 KB DRAM instead of 68 KB)
Instead of storing the 26.6 KB tl_tab attenuation table in Flash, I applied mathematical compression. The exponential decay function of the envelope allows any value to be decomposed into octaves and steps within them. I reduced the table by a factor of 26 — down to a single base octave of 1 KB. The remaining 13 octaves are reconstructed on the fly during synthesis:
signed int val = tl_tab_base[(p >> 1) & 255] >> (p >> 9);
return (p & 1) ? -val : val;
Similarly, the 4 KB logarithmic sine table is now generated in DRAM "on the fly" at startup.
Result: RAM consumption compressed by a factor of 4.25.
2. The Murder of Division
In the original code, to get the base frequency of a note, the index was divided by 768. On a microcontroller, this is far too expensive. I replaced the division with two ultra-fast bit shifts and a single read from the L1 cache of a tiny table (just 33 bytes). A multi-cycle procedure became an ultra-fast register operation.
3. Software "Clock Gating"
In the classic approach, the logic of all 32 operators is polled every sample. I introduced a channel mask. If an operator's envelope has decayed to zero (EG_OFF), the mask disables that channel. For 99% of samples, the math for silent channels simply never runs.
Result: The processor computes the sound "inside itself," fitting entirely within ~16 KB of DRAM. We never touch the external SPI bus, guaranteeing 100% stable framerate.
Act II. The Engineering Masterpiece: VGMPlay MSX
Once the "hardware" part was ready, it was time for the software. MSX has an amazing player — VGMPlay MSX by Laurens Holst [5]. What he managed to make the humble Z80 processor do in real time is nothing short of pure translation magic.
- SN76489 → AY-3-8910: The player recalculates Sega Master System sound registers on the fly into a format the standard MSX chip understands.
- OPN2 (Sega Genesis) → OPM (Yamaha SFG-05): This is the quintessence of the magic. The chips are similar (4 operators each), but their register maps and algorithms are completely different. The player intercepts commands from the Sega and translates their structure into the SFG-05 format. The Sega sound, passed through this filter, acquires a characteristic, crystal-clear "glassy" tone. A kind of High Definition version.
The Secret of Speed — MMIO
Unlike regular MSX chips hanging off I/O ports, the SFG-05 module uses Memory-Mapped I/O (MMIO). Registers are projected directly into the cartridge's memory space (addresses 0x3FF0 – 0x3FF6) [4]. For the Z80 processor, the LD (HL), A instruction (memory access) is faster than OUT (C), A (port access). This is critically important for dense musical arrangements.
My emulator adaptation code successfully intercepted these addresses, and the FM synthesis sounded divine.
Compatibility and Sound Translation Matrix
| Platform (Source) | Original Chip(s) | How It Sounds on MSX (via YM2151) | Digital Sound (PCM/ADPCM)* |
|---|---|---|---|
| Sega Genesis / MD | YM2612 (OPN2) | OPM (SFG-05) — translated | Not supported (samples are muted) |
| Sega System 1/2 | YM2203 (OPN) | OPM (SFG-05) — translated | Absent in original (no PCM chip) |
| NEC PC-88 / PC-98 | YM2608 (OPNA) | OPM (SFG-05) — translated | Not supported (samples are muted) |
| Sharp X68000 | YM2151 + MSM6258 | OPM (SFG-05) — FM native | In progress (writing an ADPCM hack) |
| Arcades (e.g. CPS1) | YM2151 (OPM) | OPM (SFG-05) — native | Not supported (OKI samples muted) |
| Native SFG-05 Software | YM2151 (OPM) | OPM (SFG-05) — native | Absent in original (FM-only) |
* Digital Sound (PCM/ADPCM) — if physically present in the original file being played.
• PCM (Pulse Code Modulation) — uncompressed digital audio (used in the YM2612 on Sega Genesis).
• ADPCM (Adaptive Differential PCM) — compressed digital audio (used in the YM2608 on PC-98 and the MSM6258 on Sharp X68000).
Key Architectural Nuances:
- YM2612 (OPN2) played samples by itself. There was no additional digital audio chip on the Sega Genesis/Mega Drive motherboard.
- YM2608 (OPNA in PC-98) had a full-fledged built-in hardware ADPCM controller. You just had to give it a memory address, and the chip itself, without distracting the CPU, would read the data and play it back smoothly.
- YM2151 (OPM in SFG-05 / CPS1) had no built-in DAC for user samples at all (its DAC was used solely to output FM synthesis results). That's why on Capcom arcade boards (CPS1), an auxiliary OKI MSM6295 chip was always placed next to the YM2151. This chip hardware-read ADPCM samples from the cartridge ROM and handled all drums and voices.
Act III. The Cruel "ADPCM Bummer" and Three Unknowns
The FM synthesis sounded crystal clear. But the music felt empty and flat. Why? Because PCM, in any form, was gone. The YM2151 (OPM) chip is a pure FM synthesizer. Its silicon physically has no blocks for digital samples. When VGMPlay takes a track from Sega Genesis, it simply mutes the drum channel because it has nowhere to output it.
Attempt #1: Pretending to be an MSX turboR
I found a note in the VGMPlay logs: the player can stream Sega Genesis samples if it detects the built-in 8-bit PCM channel of the MSX turboR computer (ports 0xA4 / 0xA5). I added emulation of these ports to fMSX, hoping for an elegant hack. But, as often happens, it didn't work on the first try, and I shelved the idea.
Attempt #2: Hardware Coprocessor Architecture
Then I decided to go all-in:
-
MSX (Z80): Intercept sample loading at the start of the composition and throw them into the emulator via a custom port
0x33. Playback commands go to port0x32. So the Z80 simply tells the port: "Play sample X" — and forgets about it. - On the hardware side: Allocate 1 MB in fast PSRAM. All incoming samples are glued into a single data bank. In theory, the emulator was supposed to decode ADPCM in the background using the MSM6258 chip core, resample the audio in hardware, and mix it on top of the YM2151 FM synthesis.
The "Girl with an Oar" Problem
VGMPlay was originally written for modern PCs. When porting it to the MSX (Z80), Laurens Holst was looking at a "Rubens painting," but sculpted his own "girl with an oar" in canonical assembly. Since the OKI MSM6258 chip (from arcade boards and the Sharp X68000) never existed on the MSX, no one ever thought to port its support into the player.
I found myself in an equation with three unknowns, without a single foothold:
- No MSM6258 support from VGMPlay for MSX.
- No ready-made implementation of similar chips in other MSX hardware emulators.
- My own lack of experience in such hacks.
Epilogue
I dug deep into the VGMPlay MSX source code and managed to implement MSM6258 calls on the player's assembly code side (which in itself requires thorough testing). The irony is that, in doing so... I failed to implement proper support for the OKI MSM6258 itself on the fMSX emulator engine side.
Full-fledged PCM symphonism didn't work out on the first try. Classic giving up too early.
But was it worth it? Absolutely.
Yes, the sound picture in tracks with heavy ADPCM is still incomplete. But for compositions originally conceived for pure FM synthesis (and there are hundreds of them!), the result exceeded expectations. We got an incredibly fast, energy-efficient synthesizer that takes up minimal RAM.
And the drums? Maybe we'll get to them later. Let's leave that for the next chapter of the optimization chronicles.
Why Bother With All This in an Emulator?
It might seem pointless — why spend days optimizing a complex arcade synthesizer like the YM2151 and fighting for kilobytes on an ESP32-S3 chip, when 90% of standard MSX games get by just fine with regular PSG or OPLL? And you could just listen to all this comfortably on another device.
To me, there are two reasons — one practical, and one engineering-driven.
The Practical Reason
It lies in the platform's strict limitations. When we run fMSX on a portable system, the microcontroller isn't just spinning the Z80 CPU and VDP video chip. A whole "orchestra" of sound coprocessors (PSG, OPLL, SCC, SCC+) is running in parallel. If even a single element of this system — say, an unoptimized YM2151 driver — starts regularly causing cache misses in external memory, the emulator's stable framerate will instantly collapse.
Compressing the chip structure down to ~16 KB in internal DRAM isn't just a sporting interest; it's a harsh necessity. Only then can the synthesizer stably generate audio in the background without interfering with video output and the operating system. Furthermore, full SFG-05 module support transforms the emulator from a "simple game launcher" into a full-fledged workstation capable of playing rare music software and complex VGM tracks.
The Engineering Reason
This one goes deeper. Porting old desktop code to a microcontroller forces us to look under the hood of both architectures. It's an excellent mental workout that teaches you to treat processor resources with care. By replacing heavy division with fast bit shifts, optimizing attenuation tables, and building software "Clock Gating," you're essentially continuing the work of the Yamaha engineers who, forty years ago, economized every transistor on the silicon die.
Yes, PCM is still absent, and full MSM6258 emulation on the engine side remains in the plans for the future. But this experience clearly shows that modern affordable microcontrollers are capable of reproducing the complex music hardware of the past to a fairly high standard.
In the end, it's precisely from such compromises that the entire classic gaming industry was once born.
Links
fMSX Emulator Port for ESP32-S3
github.com/Svarkovsky/s3-msx-pc
(The version with YM2151 support has not been released yet; it's in testing.)Yamaha YM2151 (J. Burczynski) Code Port for ESP32-S3
github.com/Svarkovsky/YM2151_ESP32
References
[1] J. Burczynski, S. Isoda. YM2151 (OPM) Emulator for MAME — original code and documentation. Retronica, 2016.
https://retronica.wordpress.com/2016/05/01/sacar-sonido-del-ym2151-en-mame/
[2] Espressif Systems. Optimizing Execution Speed on ESP32-S3: Flash Cache, IRAM/DRAM Placement. ESP-IDF Programming Guide, v5.4.
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-guides/performance/speed.html
[3] Espressif Systems. ESP32-S3 Datasheet: Technical specification of Xtensa Dual-Core 32-bit LX7 architecture and internal memory.
https://www.laskakit.cz/user/related_files/esp32-s3_datasheet_en.pdf
[4] Grauw. YM2148 (SFG-05) MIDI Module — Register Map.
https://map.grauw.nl/resources/midi/ym2148.php
[5] Laurens Holst. VGMPlay MSX — VGM player for MSX.
https://www.grauw.nl/projects/vgmplay-msx
Top comments (0)