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 constant: `e`
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 constant (lil' e as my professor called it), I think of compounding interest. The compounding 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: delta * 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"

