In the last article we dug into the YM3812 registers and how to manipulate them an electrical level. Today we are going to build up the first part of the schematic—just enough to get sound working through a microcontroller.
Whenever building on a new platform, What’s the first thing you implement? Hello World. It’s a great starting point that ensures everything works—the hardware, the development toolchain, etc. We need an equivalent project for our YM3812 that uses sound instead of text on the screen. We need… a FACE reveal! (you know… play an F major 7th chord… F A C E… yeah… woof… #DadJokes 🙄). OK but seriously… to play a note on this chip, we need working hardware, a library that interfaces with the chip, a properly configured voice, and the ability to turn on/off a note at the right frequency. This is going to be an epic journey… and it’s going to take two articles to complete. In this article, we will start with the hardware and schematic, and then upload the code from the GitHub repo using the Arduino IDE. Then in the next article, we will look into the code in a bit more detail to see exactly how it’s working.
In the spirit of starting small, I’ve pulled together a simple implementation of the YM3812 hardware. Technically, you could simplify this further—interfacing the full 8-bit data bus directly with the microcontroller, or having the microcontroller produce the 3.58 MHz clock. But we are going to need some of those pins down the line and this all fits on a single breadboard. Let’s take a look at our cast of silicon characters.
I like to think of the AVR128DA28 as an Arduino Nano, but in chip form—and far more capable. This chip sports:
128kb of program memory and16kb of ram
10 AD converters, and a DA converter
I2C, SPI, UART, and even 4 hardware serial ports
Operating voltage of 1.8v to 5.5v
Internal auto-tuned 24 MHz oscillator
UPDI programing & Arduino IDE Support
And sooooo much more…
All in an itty bitty 28pin DIP package. There are even more powerful surface mount versions of the chip as well, but this will give us plenty of oomph in our project. I have to give a shout out here to Robbie Langmore from Tangible Waves for introducing me to these microcontrollers. In a world of chip shortages, there are still ~300 in stock at mouser—as of writing this.
74HC595 Shift Register
The 74HC595 shift register is next in our lineup. This chip converts the serial output from the AVR’s SPI bus and converts it into the 8-bit parallel bus that bus required by the YM3812.
YM3812 OPL2 Sound Processor
If you are reading this article and wondering what this chip does, then I highly recommend reading part one of this series. Other than some decoupling capacitors, the YM3812 requires a a 3.58Mhz crystal oscillator.
Unlike some of the other YM sound processors, the YM3812 does not have an analog output. Instead, it needs to be paired with the Y3014B 10-bit Digital to Analog Converter. This chip takes a serial data stream coming from the YM3812 and converts it into an analog output. The Y3014B requires a voltage of 1/2 VCC on pin 8 (MP) to bias the output signal (pin 2) around. Fear not though, it produces its own reference voltage on pin 8 (RB), but you still need to buffer that voltage using an operational amplifier. Also, buffering the output signal (pin 2) with an operational amplifier is another solid plan.
TL072 / LM358 OpAmp
Did somebody say operational amplifier? OpAmps are the workhorses of Eurorack Modules and Analog Circuitry in general. Unless it’s a passive attenuator, odds are, there’s going to be 1 or maybe 10 of these versatile building blocks. In our case, will use these to buffer the reference voltage and output signal generated by the Y3014B.
After the ICs, there’s only a handful of other parts.
The six .1uF capacitors provide decoupling for the ICs
The two 10 uF capacitors stabilize the reference voltage on the Y3014b
And the 4.7 uF capacitor provides capacitive coupling on the audio output
The 100k resistor ties the output capacitor to ground and the 1k resistor ensures appropriate output impedance
The 1N4148 signal diode converts the single-wire UDPI pin into separate Tx/Rx pins that attach to an FTDI cable
Lastly, a micro-switch provides a reset button for the AVR microcontroller.
Now that we’ve talked through how the pieces work, the schematic should seem pretty straightforward. The Clock (pin 26) and Data (pin 28) of the SPI bus of the AVR128DA28 connects to the Serial Data (pin 14) and Shift Clock (pin 11) inputs on the 74HC595 shift register. PortD-3 (Pin 9) on the microcontroller connects to the Latch Clock (pin 12) on th 74HC595 and latches the output of the register. This sends the 8 Data Bits (pins 15, 1-7) to the YM3812 Data Inputs (pins 10-11, 13-18).
PortD 0, 1, 2 and 4 (pins 6, 7, 8, 10) on the microcontroller connect to the four control lines of the YM3812—Write (pin5), A0 (pin 4), Initialize/Clear (pin 3), and Chip Select (pin 7) respectively. On the YM3812, tie Read (pin 6) high to prevent rogue reads. IRQ (pin 2) can be left floating in the breeze.
Sound Processor & DAC Connections
The Output of the 4.58 MHz Crystal Oscillator flows in to the Master Clock Input (pin 24) on the YM3812. Enable (pin 1) on the oscillator should float. Tying it to ground disables the clock output.
The Serial Data and Clock output pins on the YM3812 connect to the Serial Data and Clock input pins on the Y3014B and transfers the raw sound data to the DAC. Sample & Hold (pin 20) on the YM3812 connects to the Latch (pin 3) on the Y3014B, and updates the analog value of the output.
The Y3014B produces a reference voltage of 1/2 VCC on RB (pin 7) which the TL072 then buffers and sends back to the output bias control pin MP (pin 8) on the Y3014B. C7 and C8 stabilize the reference voltage.
The Analog Output signal of the Y3014B (pin 2) get buffered through another operational amplifier on the TL072 before passing through a coupling capacitor (C8) to remove the DC bias. A high value resistor (R1) ensures our output stays centered at ground, and R2 ensures our output remains at a 1k impedance.
The Other Stuff
Of course all of the ICs require decoupling capacitors on their power pins (C1 – C6). And a reset button connects the ResetLine (pin 18) of the AVR128DA28 to ground. As far as I can tell, the reset button doesn’t require a debouncing filter, you can just directly connect it.
Lastly, the AVR128DA28 can be programmed through a one-wire Universal Programming & Debugging Interface. If you don’t have one of those programmers, you can use an FTDI to USB connector. There are many variations of this connector, some include the RTS/CTS lines (which we don’t use) and some don’t. Also, there are a couple of color variations of the wires on the connector. Here is an alternate color scheme that I have seen:
Our circuit isn’t going to do much without a bit of code. We need to program the microcontroller to see if everything works. As it happens that I created a simple library on my GitHub for just that purpose. I’ve trimmed it down to its basic elements and added loads of comments to make things as readable as possible. Feel free to download and play around with it, and in the next video we’ll dive into exactly how the code works.
Note, to get the YM3812_Breadboard.ino file to work, you will need to create a “YM3812_Breadboard” folder in your Arduino folder and then copy YM3812_Breadboard.ino, YM3812.h, and YM3812.cpp into it.
Programming the Micro
Assuming that you chose to use an AVR128DA28 as your microcontroller, you’ll need to install the DxCore in your Boards Manager. DxCore is an open sourced library written by Spence Konde that allows you to use the AVR128 line of microcontrollers in the Arduino IDE. There are detailed installation instructions on the GitHub, but this should at least get you started.
First we need to add a URL to the Additional Boards Manager URLs. To do that, open the menu: Arduino > Preferences:
Enter the url, http://drazzy.com/package_drazzy.com_index.json in the Additional Boards Manager URLs text box as shown above.
Then, locate the boards manager in Tools > Boards:… > Boards Manager
Search for DxCore and locate a package that looks similar to the one below:
After installing the DxCore boards library, you should be able to select the AVR DA-series (no bootloader) from the board menu. Also, select AVR128DA28 as the chip, and 24 MHz Internal as the Clock Speed.
The programmer that worked best for me was SerialUPDI – 230400 baud. It’s quite fast.
Now, just connect your FTDI cable as described in the schematic section of this article. And fingers crossed your program should upload directly into the micro. If it doesn’t… check that the Tx/Rx pins aren’t swapped—I’ve done that a few times.
With any luck, you have now experienced the total exhilaration of getting the YM3812 to generate that F Major 7 chord! Realistically, ANY sound at this point is great, because it shows that the hardware is working. If you got everything working, feel free to leave a comment. Similarly, if you see any issues in the schematic, let me know. After all, I’m still learning too!
In the next article, we will look through the code in detail.
In conducting a pretty extensive deep dive into Yamaha’s FM synthesis chips, I’ve come to realize that while these chips produce very unique sounds, they are also largely very similar. In fact, if you compare their register settings (and ignore the level of granular control that you get), you can see just how many of the settings are the same:
With all of these similarities in mind, I originally planned to build a single module with a variety of sound processor types all controlled by a single microcontroller. After breadboarding this idea out with a YM3812, YM2151 and even a non-Yamaha SN76494, it worked! And I was even able to translate chip settings from one chip to another seamlessly. But when I started to document how it worked, I realized from a complexity standpoint, I needed to back waaaay up and start from the beginning. To get into the advanced stuff, we need to start with a smaller project that lays a strong foundation. And what better foundation than a Eurorack module powered by Yamaha’s YM3812 OPL2 sound chip. A chip that revolutionized computer audio for the video games of my childhood.
This article is only the first in a series. Together, we will tour the properties of the YM3812, design the hardware for a module and write the supporting microcontroller code. Hopefully, each article will pair with a YouTube video on the subject as well.
Why the YM3812?
If you grew up in the 80’s and 90’s and played video games on a PC, then it is highly likely that you have listened to the glorious 8-bit audio output of a YM3812 sound processor. To me, this sound is synonymous with Sierra and Lucas Arts adventure games. I can still remember reading advertisements for the sound card in Sierra’s interAction magazine. The idea of getting music and sound effects other than square waves from a computer was like magic. So after saving up my dog-sitting money I bough a SoundBlaster Pro from Fry’s electronics. The first time using this thing was symphonic—it took adventure gaming to a whole new level. Now, 30-ish years later, I want to recapture that magic.
From a more practical standpoint, the YM3812 supports only two operators per voice making it significantly simpler to understand than other 4-operator or even 6-operator FM synthesizers. But don’t be fooled, this is still a powerful chip, capable of creating a vast variety of nostalgia-generating instrumental and percussion sounds—and that’s what makes this chip such a great starting point.
How does FM synthesis work on the YM3812?
Let’s start with a quick review of FM Synthesis and how sound processors use it to produce sound. The YM3812 is an FM Synthesis sound processor. It plays up to 9 different sounds (voices) simultaneously, with each sound composed of two different operators.
An operator is the basic building block of FM synthesis. It contains an oscillator that generates a sound wave, and an envelope that adjusts the sound’s level over time. Each voice of the YM3812 includes two of these operators, and they combine together using two possible algorithms:
Mixing is the simplest form of “synthesis.” It adds the output of the two oscillators together like a mixer. In the output, both oscillators’ sounds are audible and distinct.
Frequency Modulation uses the first operator (called the modulator) to modulate the second operator (called the carrier). In this style, only the level of the carrier operator affects the level (volume) of the output signal. The level of the modulator operator affects the brightness of the timbre of the output. The higher the modulator level, the brighter the sound will be.
Frequency Modulation Demo
Words simply can’t do this justice, so let’s try a demonstration. I’ve connected an oscilloscope to the output of the final module (spoiler alert… the module eventually does work and this story will have a happy ending). I configured the module into Frequency Modulation mode and set the modulator operator to have a slowly decaying envelope. This way, you can hear how the amount of modulation changes the timbre of the sound as it slowly decreases. After playing a few notes, I also show how using different waveforms for the modulator and carrier signal also change the overall output.
Notice how the brightness quickly drops as the level of the modulating operator decays. In the Oscilloscope, it looks like the waveform collapses back into itself. Pretty cool right? Let’s try something even more mind bending…
In the YM3812, the modulator operator (operator 1) also has the ability to modulate itself. What does that mean? This was incredibly difficult to wrap my head around, so I made an animation that starts with a sine wave and slowly increases the amount of feedback. As the amount of feedback increases, the waveform shifts from a sine wave into something more like a sawtooth wave. Then, after adding too much feedback, the wave deteriorates into inharmonic white noise. While less than musical, this white noise is a critical component of making percussive sounds on the YM3812, so it’s actually a good thing!
As an experiment to see if my animation aligns with reality, I plugged the final module into an oscilloscope. Now, we only want operator #1 because that is the operator that has the option for feedback. So, I set the module in mixing mode and turned the second operator’s signal all of the way down. This ensures that only the first operator’s signal appears in the output. I then set the first operator’s waveform to be a sine wave and then slowly increased the amount of feedback. You can see it shift from sine to saw… to crazy…
Types of Settings in the YM3812
The register settings that control sound production on the YM3812 fall into three different categories.
Operator Level settings control how the oscillator and envelope functions. Oscillator settings include things like the waveform, detuning, and vibrato, while the envelope settings include things like attack, decay, sustain, and release. Operator settings have to be defined for every operator on the chip, so there are 18 sets of these settings on the YM3812.
Channel level settings determine how the operators will work together to form a sound, as well as the things that affect that sound overall. Channel settings include the pitch of the note to play, whether the sound is turned on or off, and adding effects like tremolo and vibrato.
Global level settings control the general properties of the chip like turning on “drum mode” or affecting internal chip timers. The “Deep Tremolo” and “Deep Vibrato” settings change the amount of vibrato / tremolo applied to the channels that have vibrato / tremolo enabled. Because these two settings are global, they affect all channels at once. As for the drum mode, you get 6 drum sounds for the cost of 3 channels. It seems like a good thing, but realistically you can achieve far superior drum sounds using normal instrument settings.
What are Registers?
In order to produce sound, the YM3812 uses a set of special-purpose memory locations to store the sound settings called registers. Because space comes at a premium, and there are so many different settings to store, the YM3812 compresses multiple settings into each register byte. So, for example, let’s say we found the value stored at register 0x41 to be 0x4C.
This value represents two different settings—Level Scaling and Total Level for Operator 1 of Channel 2. To understand how these values were combined, you have to look at the value of 0x4C in a binary representation. As shown above, this translates to a binary value of 01001100. The top bits on the left (the two “highest” bits) store the value “01” or in decimal, 1 and the next 6 bits store the value 001100 or in decimal, 12. The first number maps to the Level Scaling setting and the second to Total Level. Combining variables in this way saves memory because not every setting requires 8 bits to represent its value. Conversely, this also means that the maximum integer value a variable represents depends on the number of bits used.
For example, a 1-bit number, can only be on or off, where as a 2-bit number can have 4 states (0-3). The number of allowable states doubles with each successive bit, until you get to a maximum of 256 (0 – 255) for an 8-bit number.
Note, the Frequency Number setting is the only one that uses MORE than 8 bits. This value is broken up across two register settings: an 8-bit Frequency Number LOW setting and a 2-bit Frequency Number HIGH setting. To combine these into a single 10-bit value, you shift the 2 high bits over to the left 8 times and then logically OR it with the low setting.
Finding Register Locations
Earlier, I pulled a random location 0x41 and somehow said, “oh that’s these two settings.” How the heck did I know that? Well, the answer is that I looked it up in the data sheet. But I think with a spreadsheet and a little information design, we can build a simple map to use going forward.
The chart above shows how settings map to the memory locations shown on the right. These 6 global registers scrunch together 18 different settings. The left side of the chart shows how the settings map to each bit of those 6 bytes. The D0 column represents the lowest bit in the byte and D7 represents the highest bit. So for example, if you wanted to set the Deep Vibrato flag’s setting, you take the current value of BD and do a logical OR with 0b01000000 to set the correct bit, and then write the result into the BD register.
The channel-level registers follow a similar pattern, but with one added twist. Instead of each register type having one location in memory, they have 9—one for each channel. The columns on the left still indicate how bits map to settings, but now the columns on the right show the memory location of the register that corresponds to each channel. So, for example, if I wanted to turn sound generation on for Channel 5, I would have to set bit 5 of memory location 0xB4 to a 1, and that would play the note.
One more note, the base address column in the center provides the memory location of the first channel, and we can use it as an offset to calculate the other memory locations. So if we wanted the memory location of the “B0” setting of channel 5, we could find it with the formula, “0xB0 + 5 – 1”. The minus 1 is in there because the channel names are “1 indexed” instead of “0 indexed” If you labeled them channel 0 – 8, then “channel 5” would become “channel 4” and the formula would just be “0xB0 + 4”
The operator-level register add a bit more complexity to the mix. I’ve laid them out in a similar fashion with the mapping of bits to setting types on the left and memory locations on the right, but now we have 18 different locations to contend with—one for each operator—instead of 9. For every channel, there are two operators—a Modulator and a Carrier. We can also abstract these into “Slot 1” and “Slot 2” because in other Yamaha chips, there are up to 4 operators per channel and numbers add clarity.
I’ve arranged the memory location columns in the table above to keep each operator visually connected with its associated channel. If you look closely, a curious pattern emerges. Channel 1 associates with operators 1 and 4, Channel 2 associates with operators 2 and 5, and so-on. The carrier operator number is always three higher than the modulator’s operator number.
In the chart above, the row below the operator numbers (that starts, “+0, +3, +1,…”) represents the memory offsets for each operator from the base address. So to find a memory location for a specific operator, you could add this value to the base address associated with the setting you want to change. Here again, there is another “gotcha.” The memory locations JUMP between channels 3 and 4, and skip offsets 0x6 and 0x7. A similar jump occurs between channels 6 and 7, where we skip offsets 0xE and 0xF as well. As far as I can tell, the easiest way to manage this is to put the offsets into an array that maps operator to memory location.
Full Register Map
If we put all of these pieces together, the full register map emerges:
If you want to develop code that controls a YM3812, I highly recommend printing this chart, laminating it and pinning it to you wall. You are going to need it!
Oh, one other thing, if you turn on drum mode then channels 7, 8, 9 become dedicated to drum sound production. I’ve indicated this with orange asterisks in the chart above. Again, I’d recommend against using the default percussion sounds, but hey, it’s there if you need it!
Setting Registers on the YM3812
Now that we know the mapping of settings to register locations, let’s talk about how to set those registers… electrically.
The pins of the YM3812 fit into three different types. The power pins connect to 5v and ground. The three pins in yellow provide a serial connection to the Y3014b digital to analog converter chip. The 8 pins in blue connect to the data bus, and the 6 green pins work as control lines. There are also a few unused pins in grey that we can ignore.
For this exercise, our interest lies in the 8 data lines and the A0, Write, and Chip Select control lines. The Init-Clear pin will clear the contents of the chips registers, and the IRQ and read pins are used to read status information from the chip. Unfortunately, you can’t read the contents of a register, so honestly reading information from the chip just isn’t that useful.
Selecting Registers & Sending Data
Setting the register requires two main steps: selecting the register, and setting its data. To select a register, we:
Begin with the Chip Select and Write control lines high (disabled). The A0 line could be either high or low.
Set the Chip Select line low to enable the chip
Set A0 low to tell the YM3812 that we are selecting a register
Put the address of the register we want to write onto the Data Bus
Set Write to low to tell the YM3812 to use the data on the Data Bus to select the register
Wait 10 microseconds
Set Write to high to complete the write cycle
Wait 10 microseconds. At this point the register is selected
Flip the A0 control line high to indicate that we are now writing data into the register
Put the value onto the Data Bus to write into the selected register
Set Write to low to tell the YM3812 to write the data into the selected register
Wait 10 microseconds
Set Write to high to complete the write cycle.
Wait 10 microseconds. At this point, the data has been written into the YM3812
Set Chip Select back to high to disable the YM3812 again
And that’s it! If you followed the steps above, then you have written data into a register!
Now that you know where all of the registers are on the chip, and how to manipulate them, you have the fundamental building blocks to control the YM3812. Of course getting from register setting to a working MIDI controlled module will require a bit more discussion. My hope is to take these topics one at a time in future articles. In the next article, let’s go through the module schematic, and after that we will get into some more software algorithms.
I’ve been meaning to build a noise generator for a while now—especially to make hi-hats, claps, and other percussive sounds. So when my noise2 chip arrived from electricdruid.com, I was eager to dig in. As I looked through the datasheet schematic, I was really surprised by the simplicity of the module design.
The chip has outputs for pink and white noise, so short of powering things, you just need to route the pink and white noise outputs through voltage followers for protection, maybe add a low pass filter on the white noise output, and that’s basically it!
Have you ever wondered about the difference between pink and white noise? All noise is made up of a combination of sine waves of different frequencies. Those waves are added together to create sounds of different timbres. White noise contains sine waves of every frequency all at equal volume. Now, because the frequency of a pitch increases exponentially for every octave, there are more frequencies in higher octaves than in lower ones. For this reason, white noise—to our human ears—sounds high pitched. Pink noise however, equalizes the volume of the frequencies so that each octave has the same total volume.
Adding a Filter
Seeing as there wasn’t much to the noise circuit, I decided to integrate the noise2 chip into the VC Tone Filter experiment that was occupying my breadboard at the time. This experiment uses a TDA1524 tone chip (designed for Treble / Bass / Volume adjustments in cheap stereos), to create something like a VCF/VCA combo.
After adding the noise chip, I experimented to see what kinds of sounds it could generate. I added an envelope generator on loop and, with a little fiddling, got everything from ocean waves to howling winds, to hi-hats, cymbals, claps, footsteps, and base drums. Here is a sample of some of the sounds. It’s about 5 minutes of tweaking settings—feel free to skip around.
It was clear to me that this thing needed the full module treatment.
After a bit of consideration, I came up with a list of features to include. Most of these are pretty standard for any module:
Output buffering / protection
CV inputs that you can attenuate
Selectable voltages mixed with CV inputs (so you can override)
Adjustable Resonance / Feedback
Selectable pink noise / white noise / no noise
A buffered input signal (which really just allows you to use the module as a combo VCA/VCF instead of the noise)
Eurorack power connector
The filter circuit stayed largely the same as the version in my previous experiment. I used the TDA1524 as the core of the filter, and because the chip supports two channels (left and right) I fed the output of the right channel into the input of the left channel. This doubled the effect of the filters.
While the components are largely the same, I did tweak a couple of values—lowering the capacitance of C4 and C8, and raising the capacitance of C10 and C13. Because the same signal flows through both the left and right channels, the balance control acts as a choke. When balance is turned all the way left, the signal gets amplified before being attenuated on the right side (and visa versa). The signal is only loudest when it is centered. So, by having different capacitors on the left and right side, you get an imbalance between the channels, and this gives you very different sounds as you adjust the balance control.
Inputs & Feedback Loop
The feedback loop mixes together the input signal, the white/pink noise, and the filter’s output using an inverting buffer (with unity gain) to create the filter’s input signal. The feedback potentiometer adjusts the attenuation of the output signal to determine the amount of resonance. Note, for the noise switch, you will want to use one of those three-position “on-off-on” switches so you can turn off the noise.
CV Input Mixers
All of the CV inputs (except balance) follow a standard active mixing design:
The CV input attenuates through a potentiometer before being mixed together with a voltage divider. The two inverting amplifiers (unity gain) ensure that the signals mix without cross-talk and the Zener diode (D4) ensures that the CV gets clipped before it can damage the TDA1524 chip.
Here is the full schematic that also includes the power filtering components. Nothing super crazy there, although as a best practice, I do include low voltage-drop rectifier diodes on the power input to prevent frying the module by plugging it in backwards.
After a bunch of fiddling in Kicad, I came up with a rather densely packed single-board PCB layout. The 12u width (60.6mm) feels quite usable and not too cramped. Of course, if creating the most tightly packed Eurorack device is your thing, I bet you could make it smaller by stacking two boards. Personally, I only try to do that when the savings is closer to 40% or so. Here, given the number of physical controls, and the fact you do want SOME space between them, I think you’d probably only save maybe 20% on the board width. Also, hey this is a White/Pink Noise Generator, VCF, and VCA all in one, so 12 units seemed like a pretty good value to me. 🙂
One more note, I added M3 holes at the top of the module for front-panel stand-offs because the pots I’m using are super cheap ones without panel mounts. Without better pots, you need something at the top to fasten on the front panel. Also, keep an eye on those caps sitting between the potentiometers. If they get too tall, they might run into the front-panel. This isn’t too big of a deal because there is plenty of room to bend them down against the board if you have to.
Module Demo Time
The module is extremely versatile and can be used to create lots of effects. To demonstrate as much as possible, I broke the video into two parts. The first part uses the module purely to alter an input signal with the noise turned off. In essence, it acts as a filter—even resonating at one point. In the second video, I turned the noise source on and showed how it can be shaped by the filter and even mixed with the input signal.
As always, if you have any ideas on how to push this module further, please leave a comment! The device is a blast to play with and I will keep trying to improve it. Maybe starting with a proper faceplate.
Watching LMNC build the Sega Mega Drive Synthesizer inspired me to build an FM Synth of my own. I loved how all of the controls were broken out and that you could adjust the sound on the fly. I wanted to replicate that in a Eurorack-sized module, and this is how the Smooth Operator came to be.
The Smooth Operator is simply a controller for a sound module daughterboard. It breaks out all of the sound settings into 34 potentiometers and 18 switches—no menu-diving required!
For the sound processing daughterboard, I used the OPL-3 module from the MidiBox Hardware Platform. This module houses a YMF262 sound processor, YAC512 DACs, and all of the related analog circuitry. While the YMF262 is slightly different from the YMF2612 used in the Sega Mega Drive, they are both 4-operator FM Synth chips and sound very similar.
The OPL-3 module has a compact design that provides low-noise, high-quality sound output. And, because the project is open-source, the schematics and PCB layouts are freely available. You can even buy a printed board from modular addict.
Smooth Operator Features
A 128×160 pixel TFTdisplay provides a readout of all settings and a visualization of the four operator envelopes.
All four output channels output through two stereo jacks.
Midi In/Out allows you to connect your keyboard or Midi controller
Gate/Trigger outputs allow you to connect other Eurorack modules for external audio processing.
An Arduino Nano provides the brains of the operation controlling everything while keeping the device easy to program.
Before taking on this project, I first built a similar device using Yamaha’s YM3812 OPL-2 sound processor, and I highly recommend this inexpensive kit from Cheerful Electronic as a way to get started. Having only two operators to control simplifies the physical interface and programming. And, since the OPL-3 is backward-compatible with OPL-2, you can leverage most of your OPL-2 code in the OPL-3 device. In fact, I drew heavily from DhrBaksteen’s OPL-2 code library to write my OPL-3 Arduino library.
YMF262 Sound Chip Features
This FM synth chip powered many of the sound cards from the early 1990s including the SoundBlaster 16, Pro AudioSpectrum, and probably 20 others. It defined the new OPL-3 standard that replaced OPL-2— which powered the original Adlib and Sound Blaster music cards.
Operators form the basic building blocks of FM Synthesis. Each operator includes an oscillator, ADSR envelope, amplifier, and low-frequency oscillator (for vibrato/tremolo effects). The OPL-3 chip includes 36 operators and allows you to configure them in a variety of ways.
You can mix the sound from each operator directly or use one operator to modulate the frequency of another. And when a high-frequency oscillator modulates the frequency of another high-frequency oscillator, magical things start to happen. This chip allows you to combine up to 4 operators through different algorithms to form a “voice.” In 4-Op mode, you can play 6 simultaneous voices, and in 2-Op-mode you can have up to 18!
In the 4-Op, 6 voice mode as well as the 2-Op, 15 voice mode, you also get five rhythm sounds to build some sweet drum tracks.
The oscillators of the YMF262 support 8 different waveforms including a square wave, sine wave, and a bunch of other variants. These provide tons of flexibility in creating different sound timbres.
You can also mix the voices together into any of the four output channels. This provides some rudimentary surround sound functionality, but I could imagine repurposing it for a Eurorack integration that independently processes pieces of sounds and recombines them later. Sky’s the limit!
Smooth Operator hardware breaks down into 5 key sections—all controlled by an Arduino Nano. We will go through each in detail:
A grid of potentiometers & Switches and buttons that allows you to adjust the settings for each of the operators and control the menus
Controller interface for the MBHP OPL-3 sound module
A TFT display for visual feedback of the settings
A Midi In/Out Interface circuit
A circuit to output Gate and Trigger signals
Before we go deeper, here are a few links that I found extremely helpful in my journey:
YMF262 Datasheet – This provides tons of information about the chip, how to communicate with it, and how to set everything up. Print this out, and keep it close. You will need it!
YAC512 Datasheet – This provides the information you need to build out the Digital to Analog Converter circuitry that connects to the YMF262. If you use the OPL-3 module kit above, you probably won’t need to refer to this much.
OPL3 Programming Guide – Wow was this site helpful. It goes way beyond the datasheet, and provides detailed explanations of all the YMF262 registers.
As it turns out, the Arduino makes it pretty easy to wire up and read a potentiometer. You just put a wire from one side of the pot to ground and the other side to +5v and then the middle wire directly into an analog input. The potentiometer acts as a “voltage divider” and depending on how far you turn the dial, outputs a voltage between 0v and 5v. The Arduino reads this voltage and uses its digital-to-analog converter to turn it into a number between 0 and 1,024. Easy breezy.
Similarly, a switch alternates between two different connection states: ground (0v) or 5v. The Arduino turns this into 0 or 1,024 (all off or all on). Incidentally, this type of switch is called a “single pole, double throw.” It has one connector (pole) that can be connected to one of two other connectors (throws). If you look for these switches online, search for “SPDT” or “1P2T.”
Reading Many Analog Inputs
If you look closely at the Arduino Nano, you will see only 8 analog inputs. Our device needs 56! How are we going to read 34 pots, 18 switches and 4 buttons with only 8 analog inputs??
Enter the CD4051 Analog Multiplexer. You can think of this like a switch, but instead of 2 throws it has 8, and instead of using your finger to flip it, we use a binary value. Here is a block diagram of the chip:
At the top are all of the “Channel In/Out” pins. Each of these connects to the middle pin of a different potentiometer or a switch.
On the right is the “Common Out/In” pin. This pin connects to the Arduino.
The CD4051 allows you to select which of the channels at the top connects to the common pin on the right. And it does this using the A/B/C pins on the left. These three pins use a binary number from 0 (all pins off) to 7 (all pins on) to select one of the channel lines.
What about that INH pin? Well this handy little guy disconnects all of the channels from the common pin. Why would you want to do that? Well, right now we only have 8 channel inputs, and we need 56. That means we need a bunch of these chips as well as a way to turn them on and off.
A Giant 56-input Multiplexer
The above schematic shows just the connections between the Arduino and the 4051 multiplexers. Notice that the first three address lines are all connected together across all 7 of the 4051s. The fourth address line connects directly to the INH pin of the top four chips, but then inverts before connecting to INH on the bottom three chips using a 74HC14 hex inverter chip.
The output of each of the four 4051s on top goes to one of four analog inputs on the Arduino. And, because the top and bottom rows will never be connected at the same time, the bottom row feeds into the same analog input pins.
When we get to the software side of things, you will see that putting a 4-bit binary address on the Arduino’s D2-D5 pins results in being able to read 4 analog values on pins A4-A7.
One thing to note here… In case you were thinking about choosing pins A0-A3 on the Arduino, it turns out that pins A6 and A7 can ONLY be used as analog inputs. Since that is exactly what we need in this case, I used pins A4-A7 to ensure the more flexible pins are available for other purposes.
Connecting Pots & Switches
Now, see all of those connectors marked X0-X7 on each of the 4051 chips? Those are the inputs that will connect to our pots and switches.
Connecting the pots is pretty self explanatory. One side ties to ground, the other to +5v and the middle goes to one of the inputs on the 4051. Above is an example showing one Operator’s worth of pots. I used 10k pots because they seem to allow enough current to drive the A/D converter in the Arduino. I have had mixed success with 100k pots.
Switches are just as easy as the pots. One side goes to ground, the other to +5v and it works!
Experiment gone wrong (a brief aside) On my first attempt, I thought it would be great to power the pots and switches with a nice clean +5v power rail to ensure that the Arduino’s A/D readings would be more accurate. So I created a separate +5v power rail and tied all of the pots and switches to it. I still powered the 4051s (and all of the other chips) off of the Arduino’s +5v. Theoretically, that should be awesome. But in practice, the Arduino’s +5v sometimes dropped below the other (less loaded) power rail, and when the pot’s output exceeded the 4051 chip’s power, it got hot and died. After replacing all seven of the surface mounted 4051s, I combined the two +5v rails into one and everything worked great. LESSON: don’t get fancy, just use the Arduino’s 5v rail for everything.
You’d think buttons would work the same way as switches, but of course, they don’t. In practice they tend to “bounce” between on and off as you press them. This makes navigating menus impossible, because every button press registers as multiple different presses. This sends you frustratingly passed the option you were looking for. But never fear, WE HAVE THE TECHNOLOGY, and we can fix this with a bit of “debounce” circuitry.
The bouncing comes from a brief moment where the button just starts to make contact. During this moment (also known as an eternity to a micro controller operating at millions of cycles per second) the button goes back and forth between making contact and not making contact. We need to filter out that noise during the transition period and only detect the fully-on and fully-off states.
As far as debouncing circuits go, I made this one fairly complex, but also very reliable. If all of this looks like magic, let’s re-draw the circuit and break it down into its individual parts:
The first part of the circuit ensures the button signal is either “on” (5v) or “off” (GND). Before you press the button, the output connects to 5v through a “pull-up” resistor. When you press the button, the output shorts to ground. This ensures a reliable output voltage of either 5v or 0v and no state in which the output “floats”—creating unpredictable voltages.
Even though this first part of the circuit ensures the output is either all on or all off, the output signal will flutter back and forth until it stabilizes in the on or off position. To fix that, we need a way to filter out this high frequency noise. We need a “Low-pass RC Filter.”
Low-pass RC Filter:
An RC Filter contains a resistor and a capacitor—hence “RC.” In our case, we use a 10k resistor and .1uF capacitor. With these values, the filter will smooth out any bouncing noise over 160 hz. The filter will also elongate the transition from high to low. To get a sharp transition, we need one more piece…
Inverting Schmitt Trigger:
The third part of the circuit inverts the output. This way, when you press the button, the output goes high and when released, it goes low. Of course, the 74HC14 isn’t just an inverter, it also contains a Schmitt Trigger. This means that for the input to go from low to high, it has to go above the high threshold (~2.0v) and to go from high to low it has to drop below the low threshold (~1.3v). Having two thresholds instead of one dramatically decreases the effect of any remaining noise and reduces the elongated input from the filter into a nice sharp transition.
The OPL3 Connector
The MidiBox Hardware Platform (MBHP) OPL-3 module sits at the heart of the Smooth Operator. It houses the YMF262, the D/A converters, and all of the analog circuitry needed to produce sound. We can control the device through an 8-bit data bus and a few control pins.
Note: By consolidating the audio hardware like this, we can actually build all kinds of hardware modules that can plug into the Smooth Operator. As long as they have the same form factor and use the same control circuitry they should be compatible.
Without getting into the details of the module itself, let’s instead look at how to connect to it. These connections come in four types:
Power: +5, +12, -12 and GND power connections
Audio: 4 audio output channels (both signal & ground)
Data: 8-bit bus (D0-D7) for sending data
Control: Signals that tell the module when and where to write the data
First, we need a way to control the 8 data pins without using up all of the pins on the Arduino Nano. To do that, we make use of the Arduino’s SPI bus. This serial port is very fast and we can convert the serial signal to an 8-bit parallel one using a 74HC595 shift register.
The shift register reads serial data coming into SER (pin 14) using SRCLK (pin 11) for timing. Inside the chip, there are two sets of 8 flip-flops. The first set stores the data as it comes in, shifting over one bit at a time with every pulse on SRCLK. The second set of flip-flops latches the output of the first set as soon as RCLK pulses high.
This lines up really nicely with the SPI bus on the Arduino. Connect the MOSI pin (master out) to the SER pin on the 74HC595, and the SCK pin (Serial Clock) to the SRCLK pin. Now the 74HC595 will capture the serial data from the SPI bus. In order to see that data on the output (QA-QH), we also need to latch the data by pulsing the RCLK pin. So, connect RCLK to a data pin on the Arduino—I chose D6, but it doesn’t really matter.
OPL-3 Module Control Pins
What about those control pins? Well, let’s take a closer look:
/WR: tells the module to write the data on the bus into the YMF262. Note, the “/” means that a transition from positive to ground signals a write.
A0: tells the module whether we are selecting a register or writing data to the currently selected register.
A1: tells the module to which register set we are writing. We need this because the YMF262, contains two different register sets. This was an improvement over the OPL-2 standard which only had one set.
Reset: tells the YMF262 to reset everything.
A0, A1 and Reset can all be managed in the software on the Arduino. So you can connect them to any pin that can output a digital signal—I used D7, D8 and A1.
74HC595 Control Pins
As for the other pins on the 74HC595, tie /SRCLR high—we don’t need to clear the serial data. And you can tie /OE (operation enable) to ground—we always want to listen to the incoming serial data. QH’ is used for chaining together multiple 74HC595 shift registers, so we can leave that unattached.
We only trigger the /WR pin after loading data into the 74HC595. And as it turns out, we can use the same signal to latch the data onto the shift register and tell the module to read the data bus. The caveat is that /WR needs a negative pulse instead of a positive one. No problem. If we send the signal through the 74HC14 inverter, we get just what we need.
One final note: The module’s audio channel outputs each come with their own ground connection. While the output jacks sit on the Smooth Operator PCB, these ground connections are NOT connected to the ground plane of the Smooth Operator board. This would create a ground loop and cause all kinds of unwanted noise. You gotta keep ’em separated.
Are you thirsty for more?
The TFT Display
The TFT display is a critical component of the Smooth Operator. It provides visual feedback for all 50 of the patch settings as well as a graphical representation of the four envelopes. The beauty of organizing everything on one screen is that you don’t have to menu-dive to see how things change AND if you want to “save” a patch, you can just take a picture of the screen. 🙂
The Screen Module:
The screen module itself is a 1.8″ 128×160 TFT display that I purchased from AliExpress. There are lots of different types of these screens with different interfaces, so make sure yours has all 8 pins like the image above. Those pins are (GND / VCC / SCL / SDA / RST / DC / CS / BLK). A lot of similar screens don’t expose the Chip Select (CS) pin, but you really need it. The screen shares the SPI bus with the sound module, so we need to tell it when it is time to listen.
In case you were curious, here are the screen dimensions. These came in handy throughout the design process:
Hooking up the screen:
To connect the screen to the Arduino, I used the same SPI bus as the OPL3 module. I also connected the reset, chip select, and DC pins to their own digital pins on the Arduino (D10, A0, D9).
Note that the screen operates at 3.3v instead of 5v. Luckily the Arduino has a 3.3v regulated power output that we can use to power the screen. However, the data pins still operate at 5v and we need to use voltage dividers to lower them to 3.3v.
A voltage divider consists of two resistors in series with one side connected to the signal, and the other side connected to ground. The output signal comes from the point between the two resistors which allows about 2/3 of the 5v signal (or 3.3v) to pass into the screen.
MIDI connections are really just serial connection similar to the RX/TX pins on the Arduino. In fact, the only reason we don’t just directly connect the two together is because we want to ensure any unanticipated incompatible connections don’t harm the larger circuit.
To do this, we isolate incoming messages using an “opto-isolator.” The 6N138, contains a light emitting diode (LED) and photo sensor embedded inside of it. When the light turns on, the photo sensor allows current to flow through the circuit which passes the message on to the Arduino.
Note: because the Arduino Nano uses the TX/RX pins for its USB connection, I included a dual-pole, dual-throw switch to flip between USB mode (for programming the Arduino) and MIDI mode. Since this isn’t something you interact with frequently, I just used one of those micro switches.
A trip down the rabbit hole…
To really understand how the MIDI circuit works, it might make sense to re-draw it so MIDI Out and MIDI In are connected through a cable. Imagine that TX is connected to one Arduino and RX is connected to another Arduino:
The circuit on the left side of the 6N138 is pretty simple. +5V comes in from the power rail, through (R1), through the MIDI cable and into (R3). It then passes through the LED inside the 6N138, goes back through the MIDI cable, through another resistor (R2) and into the Arduino’s TX pin.
When transmitting data, the Arduino brings the TX pin HIGH shutting off current flow (because both sides of the LED are at 5v) and LOW which turns on the LED, sinking the current into the Arduino’s TX pin. The diode (D1) ensures that if the cable was inverted, current would flow around the 6N138 without damaging it.
On the right side of the circuit, the photo diode receives the signal and amplifies it through the two transistors contained in the 6N138. These transistors switch the signal, so when the LED is on, RX goes low, and visa versa.
In the end, when TX goes Low, so does RX, and visa versa.
Gate & Trigger
Not much to the gate and trigger outputs really. A diode prevents current from flowing into the Arduino from an external source, and the resistor is very likely unnecessary. I suppose it would ensure that there is a connection to ground even if A2/A3 on the Arduino were to be placed in a floating/input state.
Honestly, if you have read all the way through this, bless you. I sincerely hope the article was useful. This was my first project using Kicad and printing PCBs and, as you can imagine, I learned a huge amount along the way. Even now, there are probably a hundred ways to make this module better. And, if you have some of those ideas, I’d love to hear them!
The most important lesson I learned in this process was, to plan on getting it wrong. No matter how hard I tried, there was always some improperly sized footprint, an incorrect trace, or some other bug. Finding it was next to impossible before printing the PCB, and perfectly trivial afterwards. So, if you find yourself meticulously inspecting every wire for the 5th time, just stop, order and assemble the PCB and see why it doesn’t work. It’s soooooo much faster that way.