Wednesday, April 1, 2026

Gakken, Portamento Concepts, and Euler (fixed rate/time, linear/exponential glide)

     Last time we squared away the tuning for our Gakken SX-150 mk II by using Rossum compensation and a basic autotune formula. Now we're free to add more features. Today we'll look at glide/portamento.

SX-150 under Arduino R4 control

Fundamentals

    Portamento is a smooth transition from one note to another. Some instruments lend themselves to this, and some don't. A slide-whistle can very easily slide between notes, but a piano cannot.

    Our Gakken oscillator now tracks over a wide range, and has no limitation on what frequencies it can play within that range. We're more so limited by the DAC and its resolution. We won't worry about that today though.

Movement I (Fixed-Rate)

    So, we need to move smoothly from note to note. Simple enough. We can pick a rate (in terms of note per second) and progress by that over time. Since we're updating a DAC, we have to take finite steps at finite intervals, but we'll think of it as continuous.

    We'll start simple with a rate of 1 note per second. If we go from, say, B to C (1 note), it takes one second. If we go from B to B up an octave, it takes 12 seconds. This is a natural result of using fixed-rate portamento, and it can be a nice effect that accentuates large leaps in pitch. On the other hand, it makes these pitch changes sluggish and limits what notes you can play within a given timespan.

Fixed-rate portamento taking longer for larger intervals

Movement II (Fixed-Time)

    The opposite of fixed-rate is fixed-time portamento. It starts with a timespan (we'll stick with 1 second) and fits the glide within it. Whether your interval is 1 note or 5 octaves, it will still take 1 second.

    The implementation is simple: find a rate that will travel the interval in the given time. If our interval is 12 notes, and we have half a second, a rate of 24 notes per second will satisfy the requirements. It's our favorite middle-school-math again: rate = distance/time 

`r = d/t`

Fixed-time portamento taking the same amount of time for large intervals

    This solves the issue with fixed-rate portamento, but we lose the ability to accentuate those large intervals. We can have the best of both worlds by taking a weighted average of the two. By changing the weight, we can get an arbitrary blend of the two responses. Let `A` = fixed-rate, `B` = fixed-time, and `w` = weight 

`A*w + B*(1-w)`

    We can do a little algebra to get a version that only requires one multiplication from our processor.

`A-w*(A-B)`

Average of fixed rate and time portamento

A Secret, More Complex Third Thing

    The previous two methods outline portamento's relationship to time, but we haven't looked critically at the other axis: pitch. Our pitch doesn't have to travel in a straight line over time. It can move along a curve. There's a specific one that's very popular due to how portamento is typically done in the analog domain: the RC curve. If we put out pitch CV through a simple RC lowpass filter, we will have portamento that follows an RC curve. Great, but what's the advantage of that, and how can we do it digitally?

An RC curve from michaelsharris.com

    The advantage is subjective, but many people simply prefer the sound of the pitch taking off quickly and settling more slowly. It's described as more "musical". It's also likely a familiar sound. It makes me think of the "Ironside siren" that featured in the Kill Bill films. 


    We can look at the spectrum of this siren to confirm that it is an RC curve.

Ironside Siren RC curve

    Let's see how we can approximate this curve with code. We'll start with the typical RC equation. `V` is the voltage applied, `R` is the resistance in ohms, `C` is the capacitance in farads, and `t` is the time elapsed in seconds. We finally dip into high school math to get our friend Euler's number: `e`

`V(1-e^(-t/RC))`

    Here that is graphed in Desmos. R and C are both 1, while V is 5. Notice that, despite getting very close, the value doesn't reach 5. In fact, it never reaches exactly 5; It just gets closer and closer as time elapses. While not a show stopper, this can cause funny problems once DAC and data type precision has to be factored in.

`V(1-e^(-t/RC))` (RC = 1, V = 5)

    We can use this equation, keep track of time, and plug it in for `t` when we want to update our portamento. It's not an equation that's particularly friendly to our microcontroller though. It involves division and raising a float to the power of another float. 

Getting ahead of the curve

    In this project we're using an Arduino R4 that's a large step up from the standard Arduino we've been using. We have a 32-bit architecture and a floating point processor, so we don't need to go to the optimization lengths we did previously. No more lookup tables, or fixed-point math. Still, let's look for a less expensive way to generate this curve. We'll start by trying to understand the formula via free association.

Easy `e`

    When I see Euler's number (lil' e as my professor called it), I think of compound interest. The compound interest formula is as follows:

`A=P(1+(r/n))^nt`

    If we plug in ones for all variables but n, then simplify it, the formula takes this form, where n is how frequently we compound.

    `(1 + 1/n)^n`

    As we compound more and more, this function approaches the value of `e`.

`(1 + 1/n)^n` approaching `e`

    The formula for continuously compounding 
interest plugs `e` back into the periodic formula. It has the starting amount: `P`, multiplied by `e` to the power of the interest rate: `r`, multiplied by the timespan: `t`.

`Pe^(rt)`

    This does relate to the RC curve. The starting value is the complement to our starting Voltage, the rate is equivalent to the rate set by RC, and time, as ever, is time. In fact, the curve is the same, just rotated. We can spin and overlay our previous RC curve onto a compounding interest curve (`Pe^(rt)`), and see that they match up nicely.

Comparing rotated RC and interest curves

`e` for Effort

    We took a little detour looking at `e`, but it brought us to compounding interest. As we saw, this can be continuous or periodical. Our RC formula is most similar to the continuous formula, but the digital domain is never truly continuous. We always have to take a series of steps, so maybe a periodic approach would be a better fit.

    Instead of using a formula that calculates our interest at any given point in time, let's think of interest as a process. We start with an amount: `P = 1`, and maybe monthly we add our interest: `r = 10%`. The calculation for the first month will be this: 

`P + (rP) = 1 + (.1) = 1.1`.

    The following month we're not starting from our principal of 1 anymore; We're instead gaining interest on 1.1, and so on for however many months.

   `1.1 + (.1 * 1.1) = 1.1 + (.11) = 1.21` 

    A code equivalent might be: "total += total * rate". Isn't that nice and simple? No exponents nor division to worry about.

    If we graph the result of this (orange), we can see that it's a very similar curve to the continuously compounding curve (blue).

total += total * rate vs `Pe^(rt)`

    In fact, this is the same kind of exponential curve, only not as steep. This is simply due to us compounding less frequently. If we compound 10 times more often, the resulting curve is a much closer fit.

compounding at 10x

    This is no coincidence; This approach is called: Euler's method. It's an iterative process of adding and adjusting values to linearly approximate curves. It's so simple and useful that it pops up in many different places, such as modeling population growth, estimating heat loss over time, or in applications of the "Lerp" (Linear Interpolate) function that's ubiquitous in graphics and animation.

The Larch Lerp

    The Lerp is a simple linear interpolation between two values, and it takes a slightly different form: `a + (b - a) * t`. `a` and `b` are the values being interpolated between, and `t` is a value from 0-1 that represents how much we want to progress between them. Looking at the formula, it only serves to draw straight lines between points. The beauty comes from how it can be called within a program. 

    Let's say `a` is the position of an object, like a spaceship in a game, and `b` is a planet that it's approaching. We can lerp their two positions every frame, and use this as the new position for the spaceship. If we use 10% (0.1) for `t`, the ship covers 10% of the distance to the planet every frame. As it gets closer, 10% of the distance becomes less and less actual space that it covers, so the ship slows. Since this is based on a percentage of the distance, it takes the same amount of time regardless of the actual distance (fixed-time). Here's a gif of exactly this, from a very nice article on lerp and "easing".

using lerp to move an object - from erraticgenerator.com

    So, we're getting diminishing returns as we iterate, and we're converging on a target value: `b`. This sounds very much like our RC curve. If we graph our distance covered on the Y axis, it looks very much the same too.

`y_1 = y_0 + (b - y_0) * t`

    Finally we can write this in code as: 

total += (target - previousTotal) * rate

That's it. One simple line of code that will give us our RC response.

Bringing it full cycle

    We can also think of that last formula as: distance_remaining * rate + total

     This might not seem significant, but it's the backbone of many DSP functions. It's so vital that dedicated DSP chips include a Multiply-Accumulate (MAC) instruction in their hardware that takes the form of: `A * B + C`. This is commonly used to implement things like highpass/lowpass audio filters.

    Now remember that the RC portamento circuit we're modeling is really just a lowpass filter, and what we're doing in software is actually DSP code pretending to be a resistor and capacitor.

"Not the wind, not the flag; mind is moving"

Friday, March 20, 2026

Gakken SX-150 mk II Tuning (Rossum Compensation)

     Last time we looked into tuning the oscillator of the Gakken. We assumed a perfect exponential response though, to simplify the concepts of autotune. The real hardware is far from ideal though.

The Gakken SX-150 mk II being subjected to tuning experiments

A measured response

    I wrote a bit of code that tests the autotune accuracy by playing each note, measuring the frequency, and logging it. I put the results into a sheet, and calculated how much each note is out by. As a rough reference point, 10% and below is acceptable. Deviation above that will likely be noticeable to the average listener.

Tuning baseline


    Well, that's pretty bad. The last third or so of our range is well out of spec (10% shown by the red line).

The core issue

    Notice that the notes go flatter and flatter as we go up the scale. The oscillator is a saw-core, and these have a quirk that can cause them to go flat on high frequencies. This is due to the time spent "resetting" the waveform. This takes a larger percent of the cycle as the oscillator plays higher frequencies. The real time spent can also be longer, due to the reset signal "fighting" against the stronger pull of the expo converter at high pitches. Here's an exaggerated example:

Saw-core going flat

    We can see the upwards slope of the lower frequency saw takes about 7 division, and the fast one takes about 3.5. This should mean that they're an octave apart. We have to add in the downward slope that is the reset time though. It's about 1.5 division for the low note, and almost 2 for the high one. This completely throws off the octave relationship and gives us 8.5 vs 5.5 instead of 7 vs 3.5.

Fair compensation

    Our actual problem seems to be in the exponential converter though. Rene Schmitz has a great article on these converters, and how to compensate for these kind of high-frequency problems. He explains that the problem is caused by the effective resistance between the base and emitter (rBE) of the transistors used. This figure varies, and is rarely spec'd in datasheets, so it's hard to say what we're up against in the Gakken.

    There is a fix for high rBE though, and it's called Rossum compensation (named after synth legend Dave Rossum). It involves feeding some of the op-amp's output current back into the converter's input. All that's needed is a diode, a resistor, and a trimmer. I used what I had available, and found that a 5k trimmer and 470k resistor work reasonably well. Here they are added into the schematic:

Gakken with Rossum compensation

    So, how much has this improved the high frequency problem we had? Let's dial in the trim pot and rerun the test. No adjustments need to be made to the autotune code.

Compensated tuning

    Look at that; The high frequency issue is gone! This suggests that high rBE is a problem in the transistors used.

    The tuning still isn't flawless, but we've addressed the issue and the whole 7 octave range is under our 10% figure. We could have compensated for this in software, and we may still, but I think this is an impressive result for such a small modification.



Monday, March 16, 2026

Gakken SX-150 mk II & MIDI Autotune fundamentals

     I'm a big fan of small, toy synths and today we have the decidedly chintzy Gakken SX-150 mark II. It's the follow up to the SX-150 that was sold with the July 2008 issue of Gakken magazine.

    There is a pre-production schematic for it, but I don't love the layout. As for board diagrams, there are none. So, I of course redrew it here.

Red-headed step ladder

    There's not a lot to the synth, but the filter is interesting. It's a diode ladder filter (made from transistors) that closely resembles the 303's.

TB-303 filter

SX-150 mk II filter

    The biggest departure comes right before the output. The 303 uses a differential pair (Q21), but the Gakken replaces it with a differential amplifier (IC1B). The original transistor pair is actually a matched pair contained in one ic: the 2SC1583. This part is now rare and expensive, so they redesigned around the extremely common "jellybean" LM358 op-amp.

    They let the other two matched pairs (Q22 and Q12) simply be separate transistors. Maybe they selected for close matches, but I'd guess they didn't, given the price point.

MIDI

    It's not much of an instrument as it is, since it's very difficult to play any specific notes. The only interface is a stylus and a resistive strip that you touch it to. Let's add MIDI instead.

    The combo of stylus and strip forms a voltage divider that yields values in the range of 2.5 to 5V. How exactly does this control the oscillator though? Let's look at the schematic.

Gakken Oscillator

    Well, it has an exponential converter. That's very convenient for us. We can send our control voltage in via the "Strip" node. This is the point at which the stylus and the resistive strip meet. We can connect to this by alligator-clipping to the stylus tip itself. But, t
here's a snag. 

Gate binds you

    The "Gate" signal is derived from the Strip voltage. If the Strip voltage dips too low, the Gate goes low and disables the oscillator. This feature exists to prevent the VCO from droning when no note is being played (because there is no VCA!).

    If we just remove R5, this decouples the pitch CV from the Gate signal. We can then use the "Strip" input to inject a gate signal instead, and connect a new resistor to the CV summing amplifier. What resistor should we use though? Well, let's look at the gain of the amp.

No pain, no gain

    The original input resistor is 1M (R5), and the feedback is 470k (R21). `470000 / 1000000 = .47`. This gain is a bit high to be driving an exponential converter, but the value of feedback resistor is very high. This makes it easy for noise to to creep in and impact the pitch of the VCO, especially when experimenting using breadboards and jumper wires. So, let's reduce R21 to 47k.

    Just from playing with the values, we can find that 120k gives a nice, wide pitch range. The gain is very similar to what it was before (.39 vs .47). It turns out it needs to be relatively high due to the voltage divider formed by R25 and R30 that lowers the gain down to .025.

Autotune

    Now that we have a way to control the VCO, what voltages do we need to send to play a musical scale? That's going to depend on the exact value of components in a given SX-150, along with temperature (to some extent). We can add trimmers to hand adjust it, but we can instead make a microcontroller do the work for us. Previously we looked at a proof-of-concept guitar tuner made with an Arduino Nano R4. We can build on this for our autotuner.

Music is Math

    The tuner code can only tell us what a given pitch is, but that's a good starting point. We can send a voltage to the VCO, measure the frequency, and then repeat with a different voltage. This gives us two points of data, and that's enough to tell us where all the notes lay (assuming a perfect exponential response). This is because only two points are needed to define the line that they fall on.

    To make things simpler, we'll be converting frequency to note number. Here's a formula that does that:

`12 * log_2(f / 440)`

    Here are two real world values that I got from one version of my tuning code. It tried to play note #12 and got note #-9.81. Tried to play note #36, got note #47.95. This gives us points (12, -9.81) and (36, 47.95). We can use our favorite middle-school-math™ to find the definition of this line, revealing the location of all notes. Remember slope-intercept form? `y = mx + b`

    The slope (`m`) is defined by the rise (change in `y`) over the run (change in `x`):

`m = (y_2-y_1)/(x_2-x_1) = (47.95-(-9.81))/(36-12) ~~ 2.41`

    The other component is the offset (intercept/`b`). We can get this by plugging one of our pairs into the equation, along with the slope that we just found.

`y = mx + b`
`47.95 = 2.41(36) + b`
`47.95 - 2.41(36) = b`
`-38.81 = b`

    We can graph this with Desmos, and see that the line: `y = 2.41x-38.81` does pass through our two points, and the intercept is at -38.81.

Desmos graph of `y = 2.41x-38.81` 

    Now we've worked out the relationship between the note that we request and the note that we ultimately get. How do we adjust the note we request so that we get the note we want though? We have to work out the opposite line that will compensate for the existing one. The inverse slope will fix the scaling, making a change of 1 note on the input cause a change of 1 note on the output.

    It's worth pointing out that we can't change the original line's formula (without changing the hardware somehow); We can only modify our value of `x` that we put into it. So, here's `1/2.41x` plugged in for `x`, giving us:

 `y = 2.41(1/2.41x)-38.81`

    Notice how the slope is a nice 45 degrees now, but it still starts on the very odd value of -38.81.

`y = 2.41(1/2.41x)-38.81` in green

    What value could we add that would make our curve start from 0? It's tempting to say 38.81, to offset our -38.81, but that's not quite it. Remember the value will be multiplied by the original slope of 2.41. So, we need to again compensate for the slope and do: 

`-b/m = -(-38.81)/2.41 = 16.10`

    We can combine this with our slope, and rearrange terms so that it's more clear this is the inverse of the original line:

`y = m((x-b)/m) + b`

`y = 2.41((x + 38.81)/2.41) -38.81`

`y = 2.41((x + 38.81)/2.41) -38.81` in green

    The Arduino can easily perform this math, and Tada; We have a synth that's in tune.

    All the program has to do is find `m` and `b`, then plug them into this formula: `(x-b)/m` where x is your note.

    In the future we may explore the tuning of non-ideal oscillators. In the real world you'll find that the SX-150 goes noticeably flat in the upper octaves. This is likely due to too much time spent resetting the saw waveform vs the short length of the cycle at high frequencies.

Sunday, March 1, 2026

Arduino Nano R4 Input Capture (Guitar Tuner)

    I bought some of the new Arduino Nano R4 boards to play with. These boards step up from AVR to ARM (RA4M1) microcontrollers while still running at 5V, a rarity these days. The complete change of architecture brings with it some growing pains. Nothing is the same under the hood, and few of the new features are meaningfully supported within the Arduino ecosystem.

    I'd like to implement some kind of autotune for a synth, but how do we measure frequency with the new R4 peripherals? Since Arduino S.r.l. (Qualcomm) isn't going to hold our hand, we have to dig into the manufacturer's datasheet.

RTFM

    There's a feature that some timers have, called "input capture". It doesn't get mentioned all that much, but it's very handy for measuring external pulses. It allows an incoming signal to trigger a "snapshot" (capture) of a timer's current count. When configured correctly, it can measure things like the timing/width of a pulse, or the period/frequency of a waveform. If we search the datasheet for this term, we can find Figure 22.17

P 445 of the Renesas RA4M1 Group 32 User’s Manual


    This is the process to set up a timer for input capture. Great, but how do we follow this, and what are these acronyms? GTCR, GTUDDTYC, GTICASR... These are memory-mapped registers that control the timers. Explanations of every timer (GPT) register exist in the datasheet starting with Table 22.4 on page 396.

P 396 of the Renesas RA4M1 Group 32 User’s Manual

    Ok, but how do we actually use these registers within the Arduino IDE? Buried in the Arduino files is a header that defines these: R7FA4M1AB.h

    Here's GTCR. We can see that it's the "General PWM Timer Control Register". The header defines this as a union. This is just a method of declaring multiple variables that live within one address. CST is a single bit of GTCR, the least significant, while MD spans bits 16 through 18 of the same address.

GTCR definition from R7FA4M1AB.h

    If we look higher in the header, we see that the GTCR union is within a struct named "R_GPT0". This serves to contain all the timer-related registers. If we want to refer to MD, like the datasheet instructs, we would use: R_GPT0->GTCR_b.MD

The Wind-up

    The following code will configure the timer using the syntax we've established. There is a "gotcha" though. The timer module has to be enabled before it will function.

  //Timer Setup------------------------------------------------------------------------------------
  R_MSTP->MSTPCRD_b.MSTPD5 = 0; //enable GPT0 module clock (General PWM Timer 321 to 320 Module Stop)
  delayMicroseconds(10);
  
  R_GPT0->GTCR_b.CST = 0; //stop timer
  R_GPT0->GTCR_b.MD = 0b000; //saw-wave PWM mode (000b)
  
  R_GPT0->GTUDDTYC = 0b11; //set 11b first (per datasheet Figure 22.17)
  R_GPT0->GTUDDTYC = 0b01; //01b for up-counting
  
  R_GPT0->GTPR = 0xFFFFFFFF; //max cycle
  R_GPT0->GTCNT = 0; //0 initial count

  //GTCCRA input capture enabled on the...
  R_GPT0->GTICASR_b.ASCARBL = 1; //rising edge of GTIOCA input when GTIOCB input is 0
  R_GPT0->GTICASR_b.ASCARBH = 1; //rising edge of GTIOCA input when GTIOCB input is 1
  
  R_GPT0->GTCR_b.CST = 1; //start count operation

    Some of these acronyms above aren't mentioned in Figure 22.17, but the datasheet can clarify them.

On Time    

    The timer is running, and is configured for input capture on the rising edge of input GTIOCA. Well, what is that? It's a pin of the microcontroller, but the Arduino board obscures the true names of the pins by holding to their naming convention of D0-D13.

    We can view the real pin names in the schematic of the Nano boardGTIOCA is P107_GPT0_A, and that maps to "D7" of the Arduino header.

Arduino Nano R4 schematic

    When this pin state changes to high, the counter's value will be dumped into register GTCCR and the TCFA flag will be set. We can wait for this flag, read the value from the register, then clear the flag.

   while (!R_GPT0->GTST_b.TCFA); //wait for input capture A

   uint32_t lastTS = R_GPT0->GTCCR[0];
   R_GPT0->GTST_b.TCFA = 0; //clear the flag

    We run the risk of not polling the flag quickly enough, and missing a captured value though. A better approach is to use interrupts.

Excuse Me

    Interrupts are another poorly covered topic when it comes to the R4 within the Arduino IDE. The first snag to getting an input capture interrupt is the GPIO. Using the "Port mn Pin Function Select Register", the pin must be switched to work as a peripheral instead of standard IO. We then need to select GTIOC0A as the peripheral. The correct value for this can be found in Table 19.6

P 370 of the Renesas RA4M1 Group 32 User’s Manual

    Plugging in the value gives us this:

  //GPIO Pin D7 (P107) Setup
  R_PFS->PORT[1].PIN[7].PmnPFS_b.PMR = 1; //Used as an I/O port for peripheral functions
  R_PFS->PORT[1].PIN[7].PmnPFS_b.PSEL = 0b11; //GTIOC0A (GPT peripheral function)

    Then we need to set up the interrupt. This process isn't exactly straightforward, but we can crib from those who have gone before us. This post on the Arduino forums by pertomaslarsson is very helpful. Referencing it, I came up with this:

//Asynchronous General Purpose Timer interrupt (from vector_data.h)
static const IRQn_Type IRQn_CCMPA = AGT0_INT_IRQn;

  //Interrupt Setup
  //assign GPT0 Capture to IRQn_CCMPA (AGT0_INT_IRQn #17)
  R_ICU->IELSR_b[IRQn_CCMPA].IELS = ELC_EVENT_GPT0_CAPTURE_COMPARE_A;
  NVIC_SetVector(IRQn_CCMPA, (uint32_t)captureISR); //point to the ISR function
  NVIC_SetPriority(IRQn_CCMPA, 12);
  NVIC_EnableIRQ(IRQn_CCMPA);

    Much like the pins, there are a finite number of interrupt vectors, and we have to select what functionality we'd like them to have. Here we assign our input capture interrupt to the generic AGT0 vector. We also give it a function(Interrupt Service Routine) to call when the interrupt happens.

    Here's a simple example of an ISR function:

uint32_t lastTS, currentTS, delta = 0; //input capture readings

void captureISR() {
  lastTS = currentTS;
  currentTS = R_GPT0->GTCCR[0]; //read from input capture register
  delta = currentTS - lastTS;
  
  //clear interrupt flag for compare match A
  R_ICU->IELSR_b[IRQn_CCMPA].IR = 0;
}

    It stores the previous reading, grabs a new reading, and clears the flag, allowing a new interrupt to fire. We can use the readings elsewhere in our code.

Alright Stop

    So far, this has all been academic. Let's finish with a functional example. I've created a simple guitar tuner that displays the frequency in Hz, the name of the note, and how far the tuning is from concert pitch.

Guitar tuner proof of concept


    The source code is available on pastebin


Monday, October 27, 2025

Famicom PCB and Schematic Redraw

    There are several schematics for the Famicom, but they're all a little different. It's not clear if this is from different board revisions, mistakes, modifications, etc. So, I thought I'd trace a board that I had already depopulated for another project.

    The main goal was to make an accurate schematic, but I decided to recreate the PCB layout too. It's not perfect. I had to fudge some footprints, but they're in the ballpark. This would serve as a good starting point for making a replacement original board.

    There are some oddities in the schematic, but nothing too interesting. A tristate buffer is abused into working as an amplifier for the audio. A few passives have a dotted outline, indicating that they're not usually (ever?) populated. The oscillator circuit is little different from other schematics. Files are on my github here.

    Here are the new traces overlaid on a scan of the PCB. This highlights the accuracies and inaccuracies.
    
Overlay


New schematic

New PCB top

New PCB bottom

Saturday, July 19, 2025

Adding MIDI to the Electro Harmonix EH400 Mini Synthesizer

    Once again we return to the EH400. This time we're looking at adding MIDI.

    This mod is for a friend and needs to retain the use of the original controls. While it's tempting to build on the CV mod from last time, that's not compatible with the original touch keyboard. We need to perform the functions of the keyboard, but we have an uncommon keyboard. It works by shorting the top foil of the keyboard to one of the 25 contacts on the lower PCB. This presents one of 25 different resistances to the oscillator, and causes a note to play. How can we fake this?

Resistance

    One way is to simulate this keyboard via some kind of variable resistance. It could be a digital potentiometer, or a DAC + a voltage controlled resistor. This problem is that this replaces the keyboard. This could be fixed by somehow switching between the original keyboard and the new resistance, or by reading the keyboard and using it to control the new resistance.

Push the Button

    A simpler solution is to control the keyboard directly, by pretending to press the "keys". We can short the foil to the pads via some kind of switch. BJTs, optocouplers, FETs, and analog switches are among the options. Considering we'll need 25 switches, we want something that's not too cumbersome. Analog switches are nice because you can get several of them in one package. Luckily, we don't need 25 completely separate switches; they'll all be sharing the foil as one of their terminals. This means we can use an analog multiplexer like the CD4051, as it has many switches with one common connection.

One of many

    It's important to pick the right multiplexer (mux) for the job. Ideally we'd find a 25:1 mux, but they really only come in powers of two, up to 16. To make things simple, we'd like it to run off of the 9V supply rail, and we'd like a low "on" resistance too. This keeps it from impacting the tuning too much. This narrows down our options to mainly derivatives of the CD4067. The 74HC4067 has a lower on resistance while still being able to tolerate the 9V supply. In practice, a normal CD4067 might be low enough though.

4067 multiplexer

Free range

    So, we can manage our 25 switches with two muxes. There's another component to controlling the pitch though, the octave switch. It shifts the keyboard by two octaves. We'll need digital control of this octave switch in order to have the full 4 octave range available over MIDI. How does this switch work though?

Front and back, and side to side

    There are two capacitors that dictate the range. One might assume that the switch selects between a larger one, and a smaller one, to change octaves. Turns out that they're both the same size though. It's their configuration that the switch changes. This is done because capacitors are somewhat low tolerance devices. Let's say they're 5%; one cap could be +5% while the other is -5%. That puts them pretty significantly out of tune with each other. Instead, the circuit uses the fact that caps in parallel sum together, and caps in series decrease in value. The math behind this turns out to work in our favor.

Series vs parallel from wiki.testguy.net

    Parallel is simply C1 + C2, but series is (C1 * C2)/(C1 + C2). Let's plug in some worst case scenario 5% 100nf capacitors: (105nf * 95nf)/(105nf + 95nf) = 9975/200 = 49.875nf series. 105nf + 95nf = 200nf parallel. 49.875nf vs 200nf. Wow, we're only a quarter of a percent out from a perfect four-to-one relationship (two octave shift). So, this change in configuration (parallel vs serial) gives a result that brings our octaves much closer to in tune than 5% capacitors would normally allow.

Another one

    We find that we need one more analog switch to control the parallel/serial capacitor configuration. This time we don't need 16 throws, but the on resistance is especially important. There are a number of candidates, but the DG409LEDY happens to be relatively cheap and available at the moment. It's a dual 4:1 mux that can handle the voltage and is rated at 17Ω on resistance.

    A simple solution would be to copy the connections of the original, mechanical switch. "On resistance" wasn't a consideration in its design though. It's important that we chain as few connections through the switch as possible, so that we don't keep stacking this 17Ω penalty. Here's one arrangement that limits this while still switching between serial and parallel.

Octave switch - Simulate here


The soft side

    As with any MIDI project, there's software and digital IO involved. This opens up new possibilities on a synth that was completely analog. We can control things remotely, generate new signals, or respond to new inputs. Given how spartan the synth is, there are only so many targets for modulation. The filter and the oscillator are the big ones.
Arduino Pro Mini

Lasting impact

    There's one more thing that the original keyboard offers, a form of velocity labeled "Sense". This is accomplished with a piezo that's stuck to the bottom of the keyboard, and routed into the envelope generator. We can take our MIDI velocity, turn it into a voltage, and pipe it into the same point. This is far from precise work, so we can generate our voltage in an imprecise way: pulse width modulation. Many microcontrollers have provisions to generate PWM and it can easily be filtered into a useable voltage.

Notes between notes

    We can fairly easily add a feature that that original instrument lacked: portamento. This is a smooth sliding effect between notes. Just one problem, we're stuck with the rigid notes we have. But, glissando done fast enough approximates the sound of portamento. This means playing every note that falls between the previous and current note. This could be as little as zero notes, or all four octaves of notes. The effect is most convincing when done quickly and over a large range. It doesn't require any extra hardware though, just code to change notes.

Other thoughts

    Key tracking could be added without too much trouble, as could other software modulation sources like LFOs, or envelopes. The only obvious destinations are the filter and the pulse width setting. These already have an envelope and an LFO respectively though.

    Key retriggering could be forced on by resetting the envelope during every note on. This would disable the legato behavior of the original keyboard.

    Some cross modulation would be possible with the addition of VCAs or digipots. The filter could be FM'd by the oscillator, or the oscillator could be AM'd by the LFO. At some point you lose the spirit of the original instrument though.

Closing circuit

A one-off MIDI board

EH400 MIDI schematic

Files

    The source code and schematics for this MIDI retrofit are available on Git Hub, here. It's intended to be built with an Arduino Pro Mini, but small changes will allow it to run on many AVRs.

Demo



    You can also hear the synth used in a few actual songs:

Bliss - Stomp

Van Halen - Sunday Afternoon in the Park