MIDI Interface & Sequencer
A device to play and sequence a simple analogue synthesizer (fairly retro!) from a MIDI keyboard
Overview
In this project, I created a MIDI interface for a simple hand-held analogue synthesizer (a Gakken SX-150 Mark II), so that it can be played from a keyboard (a Yamaha digital piano). The interface and sequencer plays just one note at a time as the synth is monophonic. The project could be easily adapted for another keyboard or for another synthesizer lacking a MIDI interface, so long as it has an accessible VCO input - a Korg Monotron perhaps, or maybe a home-made synthesizer. Be careful to ensure that the voltage generated is compatible with the synth you use! (In my case, the synth runs at 6v, while the signal it receives is a maximum of 5v).
The project dates from 2016/2017. I worked on it quite intensively for many months, then put it to one side, while it was still a work in progress and gradually forgot about it as other things took up my time! Now some years later, I've decided to review, update and document it here.
It started out as a means of playing a DIY 'synth' (actually an Atari Punk Console) from a company called TWSU (Technology Will Save Us), now sadly gone, in spite of its name. MIDI notes were generated by playing a virtual keyboard on a computer connected to an Arduino, which used Pulse-Width Modulation (PWM) to generate a Control Voltage (CV) to the 'synth' to which it was connected in turn. Instructions and code were provided online, as a free extension to the kit. Unfortunately, it never really worked, the 'synth' not being playable in any meaningful sense, but it got me thinking and I adapted the code with success to play my Gakken instead.
A little later, I came across a project on the Mr. Book website (now also sadly gone, was http://mrbook.org/) to play notes from a MIDI keyboard, again on a Gakken synth. This project also used an Arduino, but this time with a Digital to Analogue Converter (DAC), an MCP4921, to generate CV. I tried that method and it worked fine and having already made a Baby-8 sequencer for use with the Gakken synth, I realized it was would be relatively simple matter to adapt the code to (temporarily) store sequences of notes as well, so added that capability.
After much experimentation, I went back to using PWM, as it sounded quite acceptable to my ear. As I added increased ability to manipulate sequences interactively (in which I may have been been influenced by the sequencer project described here), I decided to use two Arduinos connected by a serial link, as shown below.
Most recently, I have allowed recorded sequences to be stored permanently in the on-board EEPROM, which I hadn't known about earlier.
Main breadboard with two Arduinos Nano clones: the one at left is the pre-processor, the one at right is responsible for tuning, sequencing and playing notes. Controls for the second Arduino are on the breadboad below. MIDI in is through the socket at left, CV out is through the socket top right. A 9v power supply (not shown) needs to be connected to the top power rails, while the bottom rails are used for 5v generated by one of the Arduinos.
The first Arduino receives MIDI input via a 4N28 optocoupler (to electrically isolate the keyboard from the interface) and runs a program (midi_seq_pre.ino) that acts as a filter for my Yamaha piano's quirks (such as ignoring the 252 & 254 messages it sends continuously!). The wiring used to connect the 4N28 is straight from Mr. Book. (It's very close to the circuit shown here, which uses a much larger resistor on pin 5. The latter circuit also takes the output, incorrectly I think, from after the resistor, at the 5V supply).
Components used with 4N28
MIDI socket
3.3k ohm resistor
100k ohm resistor
220 ohm resistor
1N4148 diode
Wiring the 4N28
pin 1: MIDI socket pin 4, 2nd from right, as viewed from the back of the socket (5 pins uppermost) via the 220 ohm resistor. I understand the resistor is there to protect the LED in the optocoupler.
pin 2: MIDI socket pin 5, 2nd from left, as viewed from the back of the socket (5 pins uppermost)
1N4148 diode connected across 4N28 pins 1 and 2, current flow pin 2 to pin 1
pin 4 (emitter of internal photo transistor): ground
pin 5 (collector of internal photo transistor): 5V via 3.3k ohm resistor (why?); first Arduino RX pin
pin 6 (base of internal photo transistor): ground via 100k ohm resistor (why?)
The first Arduino filters MIDI notes received by the optocoupler at the left.
MIDI socket back. Pins at top, l-r: 3, 5, 2, 4, 1. Grey lead - pin 5, white - pin 4.
The second Arduino runs another program (midi_seq_gakken.ino), which receives this filtered output and, as appropriate, generates a square waveform signal using PWM. It also plays pre-defined sequences that are stored as MIDI notes.
The signal is output through pin D9, which is connected to ground via a 10k ohm resistor and a 100k pF capacitor, forming a low pass filter (presumably) to transform the square wave to usable CV, which is taken from between the resistor and capacitor. As far as I can tell, this is as per the instructions from TWSU.
In an attempt to simplify matters, the first Arduino adds 100 to the MIDI note number for 'note off' messages and the second takes it off again if the MIDI note number exceeds a certain limit. In practice, this means that MIDI notes are restricted to between 21 (the lowest on my Yamaha piano) and 120, a span over 8 octaves, which is way more than are needed, the keyboard spanning just over 7 octaves and the Gakken synth just over 3 here.
The second Arduino is responsible for the CV out to the synth
Converting MIDI to PWM
The TimerOne library used provides PWM duty from 0 to 1023, corresponding to duty cycles of 0% and 100% respectively. (Do these correspond to 0 Volts and 5 Volts respectively?). The timer's period is set to 32 microseconds, i.e. ~ 30 kHz, which I copied from the original sketch from TWSU for use with their diy 'synth'. It would be interesting to experiment with this setting.
Keyboard note C3 note MIDI 48 is the reference point for tuning and generating CVs for notes. (Not sure why I didn't use middle C or C4, MIDI 60).
There is a linear relationship, derived empirically, between MIDI note and PMW duty setting. The following variables are used here:
note_in (byte variable): this is the MIDI number of the note to be played
C3_value (integer variable): this is set to 475. It is the PWM duty setting for the C3 note on a suitably tuned synth, corresponding to a duty cycle of ~ 46%. It can be temporarily fine-tuned if required using the control panel, the synth's tuning control being somewhat fiddly to use and difficult to access.
note_mult_factor: this is to avoid using floating point variables, which makes arithmetic slow, It's set to 100 and the default note_mult (see below) has already been multiplied by this number.
note_mult (integer variable): this is set to 950, but can also be changed by the controls. It is multiplied by the difference in MIDI note number from C3 and this product is then divided by the note_mult_factor and added to the C3_value to provide the PWM duty setting.
The PWM duty for the note to be played (from the MIDI input note, note_in) is, therefore:
C3_value-((48-note_in)*note_mult)/note_mult_factor
Using the default values, this is:
475-(48-note_in)*950/100
So, for MIDI 36, C2, the PWM duty is:
475-(12*9.5)=361
Converting PWM to CV
To convert PWM duty to the PWM voltage output at pin D9, we need to multiply by 5 (volts) and divide by 1023. With a difference of 114 PMW duty per octave (see above), this means about 0.56 volts per octave.
I'd have thought that this signal would need to be smoothed out to (ideally) a constant voltage for the synth. There's a lot of information here, but it seems that the low pass filter referred to earlier transforms the square wave to a saw wave (at best?). I need to do some more research to understand this better.
What if the synth were to run at less than 5v? The maximum PWM duty setting could be limited accordingly, e.g. if rated at 3v, limit the setting to 3/5 of 1023, i.e. 613.
Note lengths and intervals during playback of sequences
The length of a single note is determined by means of an adjustable voltage, varying from 0 to 5v, read by Arduino 2 and converted to a value between 0 and 1023 (ms). Notes in sequences can be set to this value, or twice as long or four times as long, to effectively give crotchets, quavers and minims.
The interval between notes has been fixed, arbitrarily, to one fifth of this value but is easily changed via the variable note_int_ratio, currently set to 5.
As a consequence, the duration of a double note say plus interval would be less than that of two single notes and intervals. It didn't seem to be a problem during playback, but even so, I extended the duration of a double note by an interval and that of a quad note by 3 intervals.
If you want a pause with no note played, one of the lower MIDI notes will work so long as it has not been designated for special use (see Keyboard controls below).
Tuning
use synth's on-board tuning for reference note C3
fine tune C3 as required via control panel (see below)
tune a note some distance from C3 via control panel to set the range of PMW duty settings
Control panel
pin A0: single note duration, determined by a 47k potentiometer: effectively controls the speed at which sequences are played
pin D2: sequence record (pin 2) using a latching switch; a short informational beep is played to confirm that recording has been started.
pin D3: sequence playback (pin 3) using a latching switch. The first sequence (if there is one) in EEPROM will be loaded here unless a sequence has been recorded. If there is no sequence, a long warning beep is sounded repeatedly.
pin D4: sequence playback, raise by major 4th or 7 semitones (pin 4) using a latching switch. Great for 12 bar blues and also you can get interesting rhythmic effects if the original sequence contains a note which is just below the lowest playable note.
pin D5: sequence playback, raise by major 5th or 9 semitones (pin 5) using a latching switch. See above for D4.
pin D6: experimental interruption of sequence playback: play a note from the sequence if the corresponding key is being pressed on the keyboard. In the program, I've referred to this as gating, for want of a better word. Other options would be to repeatedly play the same note in a sequence or even to allow playback from the keyboard between sequence notes, both of which I have tried before.
pin D7: select fine tune C3_value (C3, midi 48, default 475) using a latching switch
pins D8, D10: +/-adjustment using momentary switches, of C3_value or note_mult as selected by switches on D7 or D11
pin D11: select fine tune note_mult using a latching switch
Control Panel. Top, l-r: Record, Playback, + Major 4th, + Major 5th, experimentation; Bottom, l-r: Fine Tune C3_value, + Tuning, - Tuning, Fine Tune note_mult. Middle, right: Playback Speed Control
Keyboard controls
These use 'special' MIDI keys rather than dedicated buttons:
21: 1st piano key (white) on Yamaha: steps through recording note length 1/2/4 duration, and for each time the key is hit, the appropriate number of informational beeps, corresponding to the note length selected, is played.
22: 1st black key: step through sequences in EEPROM. If no sequences, a warning beep is sounded.
25: 2nd black key: write sequence from temporary memory to EEPROM. If no sequence, a warning beep is sounded.
27: reinitialize EEPROM - effectively removes stored sequences. Needs to be used with care and really a confirmation step is needed.
(Note: the lowest playable note on the Gakken is ~ MIDI 43).
EEPROM
As with arrays, the first location in EEPROM is number 0.
The amount of EEPROM depends on the Arduino. I understand my model of Arduino Nano has 1024 bytes, but I've not used anything like this amount, so cannot confirm.
In the code for Arduino 2, the value of the next free storage location (eeprom_next_free) is stored in location 0 of EEPROM. It is initialized to '1' and is updated automatically as sequences are added.
Each stored sequence consists of: the number of notes in the sequence, followed by, note and note length pairs. An 8-step sequence will therefore need to use 17 locations in storage.
Note: an artificial limit has been placed to allow up to 9 8-step sequences to be stored in EEPROM. This is achieved with variable eeprom_lim, currently set to 153. For each additional 8-step sequence to be stored, this number will need to be increased by 17. It should be possible to store up to 60 8-bit sequences on my Arduino Nano, but it would be arduous to have to step through so many.
In use
I'm no musician, but overall in using this device I found the synthesizer quite playable and the sequencer mostly solid, although the timing when adjusting the controls was perhaps slightly less so. This could have been because of the overhead with handling the inputs from the control panel, but also possibly because of loose connections - ideally the components there would be soldered.
It does need a solid power supply - a part drained 9v battery will not work well!
I was thrilled to be able to record and play back (at appropriate speed) the On the Run sequence (MIDI keys 52, 55, 57, 55, 62, 60, 62, 64) from Pink Floyd's DSOTM and twiddle with the filter controls on the synth!
It's great fun to play 12-bar blues with an appropriate sequence, using the major 4th and 5th buttons on the control panel.
Comparing the sequencer with the Baby 8 Sequencer, it offers more steps in a sequence, variable note length and the ability to manipulate the sequence.
Possible enhancements
Add a display (numbered LEDs?) to indicate the sequence selected from EEPROM. Perhaps also use new buttons on the control panel to navigate up and down through the list, although I'd need to change the data structure in EEPROM, as this currently only lends itself to scrolling forward through stored sequences.
Allow individual sequences to be deleted from EEPROM.
To reduce overhead on the second Arduino and ensure solid performance as a sequencer, the control panel switches connected to the digital pins could be removed and replaced by dedicated keys on the keyboard instead, as I have already started to do for other functions. (The potentiometer, which adjusts the playback speed via the voltage read on an analogue pin, would have to stay though!). A control panel could still be used, but attached to a dedicated, 3rd Arduino which would send output (as special MIDI key numbers) to Arduino 2. The potentiometer could also be moved here and the value read from the analogue pin on Arduino 3 sent, along with any generated MIDI key numbers. To receive this input, we would need an additional serial port on Arduino 2, on an unused digital pin, as described here.
Also, for efficiency, perhaps use an array to determine the PWM duty setting for each note, as currently this is calculated each time. The array could be generated at the start of the program, with index derived from the MIDI note number, and containing the appropriate duty setting for each playable note as determined using the method described earlier. The required duty setting could then be obtained for each MIDI note to be played. If the tuning needed to be changed via the control panel, the array would have to be re-created using the new values of C3_value or note_mult.
Allow the ratio of single note length to interval, currently fixed at 5 (times) to be adjusted (using a MIDI key)?
Adding a de-tuned and identical second synthesizer...
Finally, does impedance-matching need to be considered?
Downloads
Arduino 1: midi_seq_pre.ino
Arduino 2: midi_seq_gakken.ino