OPL2 Audio Board: an AdLib sound card for Arduino
Vincent Bernat
In a previous article, I presented the OPL2LPT, a sound card for the parallel port featuring a Yamaha YM3812 chip, also known as OPL2—the chip of the AdLib sound card. The OPL2 Audio Board for Arduino is another indie sound card using this chip. However, instead of relying on a parallel port, it uses a serial interface, which can be drived from an Arduino board or a Raspberry Pi. While the OPL2LPT targets retrogamers with real hardware, the OPL2 Audio Board cannot be used in the same way. Nonetheless, it can also be operated from ScummVM and DOSBox!
Unboxing#
The OPL2 Audio Board can be purchased on Tindie, either as a kit or fully assembled. I have paired it with a cheap clone of the Arduino Nano. A library to drive the board is available on GitHub, along with some examples.
One of them is DemoTune.ino
. It plays a short tune
on three channels. It can be compiled and uploaded to the Arduino with
PlatformIO—installable with pip install platformio
—using the
following command:1
$ platformio ci \ > --board nanoatmega328 \ > --lib ../../src \ > --project-option="targets=upload" \ > --project-option="upload_port=/dev/ttyUSB0" \ > DemoTune.ino […] PLATFORM: Atmel AVR > Arduino Nano ATmega328 SYSTEM: ATMEGA328P 16MHz 2KB RAM (30KB Flash) Converting DemoTune.ino […] Configuring upload protocol... AVAILABLE: arduino CURRENT: upload_protocol = arduino Looking for upload port... Use manually specified: /dev/ttyUSB0 Uploading .pioenvs/nanoatmega328/firmware.hex […] avrdude: 6618 bytes of flash written […] ===== [SUCCESS] Took 5.94 seconds =====
Immediately after the upload, the Arduino plays the tune. 🎶
The next interesting example is SerialIface.ino
.
It turns the audio board into a sound card over serial port. Once the
code has been pushed to the Arduino, you can use the play.py
program
in the same directory to play VGM files. They are a sample-accurate
sound format for many sound chips. They log the exact commands
sent. There are many of them on VGMRips. Be sure to choose the
ones for the YM3812/OPL2! Here is a small selection:
- 0:00, Railroad Story from the demo songs shipped with the AdLib sound card (1987)
- 1:04, the opening of Star Wars: X-Wing (1993)
- 2:19, the opening of The Secret of Monkey Island (1990)
- 3:58, the opening of Indiana Jones and the Last Crusade (1989)
Usage with DOSBox & ScummVM#
Notice
The support for the serial protocol used in this section
has not been merged: instead, a simpler protocol was designed.
You need to grab SerialIface.ino
from my pull request: git checkout
50e1717
.
When the Arduino is flashed with SerialIface.ino
, the board can be
driven through a simple protocol over the serial port. By patching
DOSBox and ScummVM, we can make them use this unusual sound
card. Here are some examples of games:
- 0:00, with DOSBox, the first level of Doom 🎮 (1993)
- 1:06, with DOSBox, the introduction of Loom 🎼 (1990)
- 2:38, with DOSBox, the first level of Lemmings 🐹 (1991)
- 3:32, with DOSBox, the introduction of Legend of Kyrandia 🃏 (1992)
- 6:47, with ScummVM, the introduction of Day of the Tentacle ☢️ (1993)
- 11:10, with DOSBox, the introduction of Another World 🐅 (1991)
Another World (also known as Out of This World), designed by
Éric Chahi, is using sampled sounds at 5 kHz or 10 kHz. With a serial
port operating at 115,200 bits/s, the 5 kHz option is just within our
reach. However, I have no idea if the rendering is
faithful.
Update (2018-05)
After some discussions with Walter van
Niftrik, we came to the conclusion that, above 1 kHz, DOSBox
doesn’t “time-accurately” execute OPL commands. It is using a time
slice of one millisecond during which it executes either a fixed
number of CPU cycles or as many as possible (with cycles=max
). In
both cases, emulated CPU instructions are executed as fast as possible
and I/O delays are simulated by removing a fixed number of cycles from
the allocation of the current time slice.
DOSBox#
The serial protocol is described in the SerialIface.ino
file:
/* * A very simple serial protocol is used. * * - Initial 3-way handshake to overcome reset delay / serial noise issues. * - 5-byte binary commands to write registers. * - (uint8) OPL2 register address * - (uint8) OPL2 register data * - (int16) delay (milliseconds); negative -> pre-delay; positive -> post-delay * - (uint8) delay (microseconds / 4) * * Example session: * * Arduino: HLO! * PC: BUF? * Arduino: 256 (switches to binary mode) * PC: 0xb80a014f02 (write OPL register and delay) * Arduino: k * * A variant of this protocol is available without the delays. In this * case, the BUF? command should be sent as B0F? The binary protocol * is now using 2-byte binary commands: * - (uint8) OPL2 register address * - (uint8) OPL2 register data */
Adding support for this protocol in DOSBox is relatively simple (patch). For best performance, we use the 2-byte variant (5000 ops/s). The binary commands are pipelined and a dedicated thread collects the acknowledgments. A semaphore captures the number of free slots in the receive buffer. As it is not possible to read registers, we rely on DOSBox to emulate the timers, which are mostly used to let the various games detect the OPL2.
The patch is tested only on Linux but should work on any POSIX system—not Windows. To test it, you need to build DOSBox from source:
$ sudo apt build-dep dosbox $ git clone https://github.com/vincentbernat/dosbox.git -b feature/opl2audioboard $ cd dosbox $ ./autogen.sh $ ./configure && make
Replace the sblaster
section of ~/.dosbox/dosbox-SVN.conf
:
[sblaster] sbtype=none oplmode=opl2 oplrate=49716 oplemu=opl2arduino opl2arduino=/dev/ttyUSB0
Then, run DOSBox with ./src/dosbox
. That’s it!
You will likely get the “OPL2Arduino: too slow, consider increasing
buffer
” message a lot. To fix this, you need to recompile
SerialIface.ino
with a bigger receive buffer:
$ platformio ci \ > --board nanoatmega328 \ > --lib ../../src \ > --project-option="targets=upload" \ > --project-option="upload_port=/dev/ttyUSB0" \ > --project-option="build_flags=-DSERIAL_RX_BUFFER_SIZE=512" \ > SerialIface.ino
ScummVM#
The same code can be adapted for ScummVM (patch). To test, build it from source:
$ sudo apt build-dep scummvm $ git clone https://github.com/vincentbernat/scummvm.git -b feature/opl2audioboard $ cd scummvm $ ./configure --disable-all-engines --enable-engine=scumm && make
Then, you can start ScummVM with ./scummvm
. Select “AdLib
Emulator” as the music device and “OPL2 Arduino” as the AdLib
emulator.2 Like for DOSBox, watch the console to check if
you need a larger receive buffer.
Enjoy! 🎶