Arduino PSS-680 Synth Editor- Part 1

I’m still exploring the range of Yamaha (and a few others) keyboards from the 80s with interesting synth chips.  So far I’ve looked at the YM2413 and the PSS-170.  I also have some YM3812s and a PSS-460.

But today I’m looking at the PSS-680 which has a YM3420 in it, with the eventual aim of having some kind of “real knobs and buttons” interface to the synth part of it using an Arduino. This is the first post in a series working towards that.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

Parts list

The Yamaha PSS-680

The PSS-480 and PSS-680 are pretty similar keyboards. They both use the YM3420 chip (“OPU”) for two-operator FM synthesis and the YM3419 (“RYP7”) for drums. The 680 is a 61-minikey (5 octave) keyboard with MIDI IN/OUT/THRU and a pitch bend wheel.

There are a number of resources on the Internet for both keyboards, including a user manual (with full details of the MIDI implementation) and a service manual for the 480. Here are some useful links for more information.

There isn’t an awful lot about the YM3420 chip itself though. There is a little more detail in the following:

What I’m particularly interested in at the moment is the synth and MIDI section.

The synthsizer section provides two FM operators linked in a linear manner with a feedback parameter and control of the modulation as shown in the diagram. Effectively operator 1 is the modulator and operator 2 is the carrier.

The following can be changed per operator:

  • Frequency – this is actually a frequency ratio/multiplier ranging from 0 to 15. When set to 1 the frequency is “at pitch”. 2 doubles (octave up); 3 triples (octave + a bit); 4 is two octaves; etc. 0 halves the original frequency.
  • Attack/Decay – 0 to 63 for each.

And the following can be changed to change how the operators interact:

  • Feedback Level – 0 to 7 – how much of op1 feeds back to itself.
  • Modulation Level – 0 to 7 – how much op1 modulates op2.
  • Final Level – 0 to 7 – the final (combined) output level from op2.

What is interesting though is that if you look at the MIDI chart for SysEx Voice Data (Format 00 – “FM 2OPERATOR VOICE DATA” – page 41 in the user manual) there are additional fields that hint at other parameters that might be changed.

Some that look particularly interesting are:

  • DT1/DT2 – fine and course detune
  • LKS/RKS – key scaling
  • D2R/SRR – additional steps in the envelope
  • SIN TBL – Sine table form

To get a feel for what these extra parameters might be able to, there are some PSS 480/680 voice editors available online to try:

That last one is best for seeing the whole range of parameters, but unfortunately it isn’t practical to use on the PSS-680 at present, as it keeps forcing a change back to voice 00 whenever it changes anything. To be useful with the PSS-680 it needs to use MIDI voice 100 which is the first “bank” voice.

But the web-based editor screen is a very sleek graphical representation of all the voice parameters available (shown below). How I’d like to have this keyboard!

PSS-680 System Exclusive Messages

As mentioned, a lot more of the synth engine is exposed via SysEx messages than is apparent from the front panel, so I wanted to explore the possibility of adding some Arduino-driven controls for these extra bits.

There is a problem however. These parameters are only (as far as I can see) available via a complete voice dump. The Systems Exclusive message or that can be found on page 41 of the user manual, but there are a few details that are glossed over that means a little working out is required.

PSS 680 FM 2OPERATOR VOICE DATA

SysEx Header:
  F0H - Start SysEx
  43H - Yamaha ID
  76H - Instruction Class
  00H - Format Number (0 = "FM 2OPERATOR VOICE DATA")

SysEx Data:
  xxH x66 - 66 bytes of 7-bit formatted data

SysEx Footer:
  xxH - 7-bit checksum value
  F7H - End SysEx

If you’re following along with the manual, you might be wondering why the manual lists 33 bytes of data yet I’ve stated 66 bytes above. This is because each byte of SysEx data is split over two actual MIDI data values “on the wire” as follows:

SysEx Data
  xxH - contains 4 most significant bits of Voice Dump data parameter.
  xxH - contains 4 least significant bits of a Voice Dump parameter.

This is because the voice parameter block is best thought of as a block of 8-bit data bytes, but MIDI SysEx messages can only support 7-bit data. The solution is to use two 7-bit values “on the wire” to represent one 8-bit value of voice data, with the 3 most significant bits of the 7 set to 0.

// Reception
  for (int i=0; i<SYSEXBANK_SIZE; i++) {
    int seIdx = SYSEXHEAD + i*2;
    sysExVoice[i] = (msg[seIdx]<<4) + (msg[seIdx+1] & 0x0F);
  }

// Transmission
  for (int i=0; i<SYSEXBANK_SIZE; i++) {
    se[SYSEXHEAD+i*2] = sysExVoice[i] >> 4;
    se[SYSEXHEAD+i*2+1] = sysExVoice[i] & 0x0F;
  }

There is one additional complication for transmission – the checksum needs to be calculated. The PSS-680 MIDI implementation describes the checksum algorithm as follows: “CHECK SUM DATA = TWO’S COMPLEMENET OF 7BITS SUM OF ALL DATA BYTES.”

In implementation terms, this looks like the following:

byte cs = 0;
for (int i=SYSEXHEAD; i<SYSEX_SIZE-SYSEXFOOT; i++) {
  cs = (cs + se[i]) & 0x7F; // Ignore anything overflowing 7-bits
}
// Twos complement:
//   Invert value
//   Add 1
//   Take 7 least significant bits
cs = ((~cs) + 1) & 0x7F;

So far, this describes Format 0, but there are also additional formats as follows:

  • Format 0 – FM 2OPERATOR VOICE DATA
  • Format 1 – MELODY MEMORY 5BANKS
  • Format 2 – CHORD MEMORY DATA
  • Format 3 – RHYTHM PATTERN DATA

The PSS-680 will receive SysEx messages in the above format and can also send them, but only (as far as I can see) in response to a “Memory Bulk Dump” button press. This involves pushing the “Transmit Ch/Memory Bulk Dump” button twice, followed by “Value +”. This results in the following messages being transmitted:

  • 5x Format 0 messages: Voice Data for each of the five user bank voices (72 bytes each)
  • 1x Format 1 message: (10056 bytes I think?)
  • 5x Format 2 messages: (410 bytes each)
  • 1x Format 3 message: (722 bytes?)

Arduino SysEx Message Handling

The Arduino MIDI Library supports SysEx messages using all the same mechanisms available for other MIDI messages – either via polling (checking MIDI.read()) or a callback function. But documentation and examples are currently quite sparse.

The basic structure is as follows:

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();

void midiSysEx ( byte *msg, unsigned msgsize) {
  // SysEx message in msg of size msgsize
}

void setup () {
  MIDI.begin(MIDI_CHANNEL);
  MIDI.setHandleSystemExclusive(midiSysEx);
}

void loop () {
  MIDI.read();
}

There are a few complications however.

The main one being that the Arduino, being limited in memory capacity, cannot support a large temporary buffer for handling SysEx messages. The default memory buffer within the library is 128 bytes, but it can be changed if required using the MIDI settings structure when initialising the MIDI library.

If a SysEx message is too large then the library will “chunk” the message acoridng to the following scheme:

First chunk: F0....F0
Second chunk: F7....F0
Other chunks: F7....F0
Last chunk: F7....F7

F0 is the SysEx start byte and F7 is the SysEx end byte, as per the MIDI specification, but they are used here to indicate additional splitting of the message. To reconstruct the entire SysEx message requires removing all but the very first F0 and very last F7 and concatenating the chunks together.

But of course this requires a memory buffer large enough for the entire message.

There is another issue however that will arise if attempting to process multiple large SysEx messages and that is performance. The handling of messages within the callback must be kept as quick as possible to ensure that the MIDI library is still able to service the hardware serial port in time.

The main symptom of performance issues is incomplete MIDI messages. In the case of SysEx messages this may well start to look like large chunked messages rather than smaller complete messages. Here are screenshots of the PSS-680 memory dump in MIDIOx and then attempting to do the same thing with an Arduino via callbacks.

As can be seen the Arduino correctly recognises the first message, but then the next is missing the SysEx End marker so gets lumped in with the next, but then after that it seems to have missed any further legitimate F0/F7 markers and is now “chunking” the data instead.

Note, this is printed out over a serial link running at 115200. If the serial link is 9600 (so takes longer to “print”) then chunking starts pretty much as soon as anything is sent to print.

The usual approach to this is to quickly store the message on reception, whilst time is of the essence, and then process it in “slower time” afterwards. One way to achieve that is by using a circular buffer or queuing mechanism. But again, the key here is to have a buffer that is large enough to receive the bulk of the data before it can be parsed.

I was chatting with the maintainer of the library on Mastodon and he kindly posted this gist of code that could be used to print out SysEx messages: https://gist.github.com/franky47/24344dcc66be77c06def49a3510bc119

Regardless of the implementation everything falls back on the limitation of the Arduino (the Uno at least) having very little working memory for buffering data.

Thankfully in my case I am only interested in the first message of the memory dump – that containing voice data for bank 1. It is reasonable to quickly check received messages for the required header and bank number and once found not worrying about the performance impact of handling the message.

Adjusting Extra Parameter with an Arduino

Ideally, I’d be able to write single parameters to the PSS-680 from an Arduino, but that isn’t possible.

The next best thing might be to write a single voice dump – that is possible, but it has a limitation – everything in that voice dump will override all existing parameters from the voice it is changing.

So there are two options:

  • Configure everything from the Arduino and write all parameters to the synth at the same time. This tends to be what the voice editors on PCs do.
  • Read all the existing parameters off the PSS-680 adjust the setting of interest, and write them all back.

But there is no way to request a voice dump from the PSS-680 as far as I can see. It is possible to trigger the dump over MIDI from the synth’s UI but that outputs the entire memory as described above.

I am not going to attempt to reproduce an entire synth editor on an Arduino (at least, not right now), so I’m going to take the second approach. This means that the workflow will go someting like this:

  1. Select the voice to use as a template.
  2. Store this voice in Bank 1.
  3. Initiate a full memory dump to the Arduino and extract the voice parameters from Bank 1.
  4. Change the required parameters from the Arduino.
  5. Write the voice dump back to the synth.
  6. Re-select Bank 1 either automatically (via a MIDI Porgram Change message) or the buttons on the synth.

As a first experiment, I’ve not implemented any controls on the Arduino, I’m just going to take the SIN TBL parameter for the carrier operator (op2) and cycle through the four values: 0, 1, 2, 3 whenever the Arduino receives a memory dump from the synth.

The Code

First up, these are the limitations of what I’ doing:

  • The writing back can’t happen immediately – it has to wait until the full memory dump is complete.
  • I’m using a SoftwareSerial MIDI interface with my Simple MIDI Serial Monitor PCB.
  • Once the bank has been detected, the SysEx Voice Data is dumped out to the serial port for debugging purposes.
  • It is only working with Bank 1.
  • An automatic Program Change message setting voice 100 is used to select the new Bank 1 which is sent on MIDI channel 1.

I’ve already discussed the SysEx handling. In terms of accessing the voice paramters, I’m storing the voice bank as an array of 33 bytes, exactly as shown in the user manual. To access any specific parameter I’ll write a get/set function to translate between the exposed idea of the paramters (“Sine Table” say) and the appropriate storage locaton.

Here are the access functions for the Sine Table.

byte getSinTbl (int op) {
  if (op == 2) {
    return sysExVoice[12] >> 6;
  } else {
    return sysExVoice[11] >> 6;
  }
}

void setSinTbl (int op, byte sintbl) {
  if (sintbl > 3) {
    return;
  }
  if (op == 2) {
    sysExVoice[12] = (sysExVoice[12] & 0x3F) + (sintbl << 6);
  } else {
    sysExVoice[11] = (sysExVoice[11] & 0x3F) + (sintbl << 6);
  }
}

At present this is all that is implemented. In terms of a delay, I have to wait 5 seconds before updating the parameter and sending it back.

In the video, the PSS-680’s display goes blank whilst it is sending the memory dump. Then it is just possible to see the display flash as the values are written back and the PC message sent. Sometimes you can hear the original sound prior to the PC message kicking in and switching to the new sound.

The video shows the synth cycling through all four stored sine tables, which are:

  • Sine Wave
  • Squared Sine Wave
  • Sine Half Wave
  • Squared Sine Half Wave

I can’t quite work out if these correspond to the four waveforms available with OPL2 synths or not. There are some pictures of the standard waveforms for OPL2 and OPL3 synths on Wikipedia, but they don’t look like these descriptions to me. I might have to get out an oscilloscope and have a look myself!

Find it on GitHub here.

Closing Thoughts

It has taken a little while to get this far as information about the YM3420 (OPU) chip seems pretty hard to come by. But the PSS-680 manual is pretty detailed on the MIDI front which has been really useful.

In the video I’ve used the pure sine wave voice and it shows no frequency multiplying, no feedback and no modulation, which means the differences being heard are just down to the waveform used for the carrier operator.

This proves that adjusting the parameters is possible, so the next stage is to get some pots and start working out which parameters make sense to build a physical interface for.

Kevin

Leave a comment