Case Study

Stasis Tuner

A strobe tuner that knows when to stand still.

Stasis waiting for a note, dark mode Stasis waiting for a note, light mode

Most guitar tuners are a needle and a guess. You pluck, the needle swings, and you wait for it to settle somewhere near the middle.

Strobe tuners have always been the exception. Instead of a needle, a pattern spins: the further off you are, the faster it moves, and when you’re in tune, it stands perfectly still. They’re the most precise tuners there are, and also the most intimidating.

I built Stasis, a strobe tuner for iPhone that anyone can read at a glance. In tune simply means nothing moves.

Harder than it sounds.

Measuring a pitch is the easy part. Knowing which note someone is actually playing is not. A guitar string doesn’t make one clean tone, it makes a whole family of them, and the phone hears everything else in the room along with it: the body of the guitar, a fridge, a fan, the next string still ringing.

For a while, Stasis would flash an A when I played a high E, cling to a note long after it had faded or follow the hum of my house instead of my guitar. I rebuilt the way it listens more times than I’d like to admit, and the version I kept is simpler than most of the ones I threw away.

Built around stillness.

Stasis does one thing. It tells you exactly how far a string is from its note.

Everything on screen follows a single idea: motion means out of tune, stillness means in tune.

The screen itself is the interface. Tune to start, hold for menu.

The gauge showing an E a little flat (dark mode) The gauge showing an E a little flat (light mode)

A gauge you can read.

At the center sits a wheel of note names with a ruler, read against a fixed pointer, like the needle of a classic tuner bent into a circle. Every line is five cents.

The wheel follows the string exactly, wobble and all. The rings around it show where the note settles.

The two strobe rings standing still on an A that is in tune (dark mode) The two strobe rings standing still on an A that is in tune (light mode)

Still means in tune.

Two rings spin around the edge of the screen. The outer ring follows the note itself, and the inner ring its second harmonic, so it moves twice as fast and catches the smallest error. Sharp spins one way, flat the other. As you get close they slow down, and when you’re there, they stop.

The rings have weight, too. They speed up and coast like a fan instead of snapping to every change, so the strobe feels like a physical thing rather than a readout.

The settings card (dark mode) The settings card (light mode)

Hold for menu.

Long press anywhere for settings: the reference pitch, the ruler, sharps or flats, and how close counts as in tune for each ring. Swipe across to personalize.

A B flat in the serif typeface (dark mode) A B flat in the serif typeface (light mode)

Your tuner, your type.

Colors for the strobe, the gauge and the pointer, sharps or flats, and a choice of typeface: San Francisco, or New York, Apple’s serif.

New York has no flat sign. Ask for one and iOS quietly borrows it from a Japanese typeface, with a gap wide enough to park a car in. In serif mode, Stasis draws its flats from STIX, a classical serif with a flat that sits right where it should.

Under the hood.

Stasis is built in SwiftUI, with the signal processing written in Swift on top of Apple’s Accelerate framework. A few problems took longer than everything else combined. These are the three I’m proudest of.

Knowing when a note is over.

A tuner has to decide when you’ve stopped playing. Most do it with loudness: once the sound drops below some level, the note is over. That works in a quiet room. In a real one, the tail of a string sinks into the hum of a fridge long before it actually stops, and the tuner either lets go too early or starts following the noise.

The breakthrough was realizing Stasis already had a better ear than a loudness meter: the strobe itself. It’s tuned to the exact note being played, with filters so narrow that room noise barely reaches them. So instead of asking “is it quiet?”, Stasis asks the strobe “is my note still there?”, and follows a string all the way down to its last breath.

There was one catch. My house hums at almost exactly B♭, and when I played a B♭, the strobe kept hearing the hum after the string had died. The fix came from physics: a plucked string always decays, and a hum never does.

// The strobe is tuned to the note being played, with filters so narrow that
// room noise barely reaches them. Its strength answers one question:
// is my note still there?
let strongest = dsp.snapshot().amplitudes.max() ?? 0

// A plucked string always dies away. A hum at the same pitch doesn't.
var steadyTone = false
if strengthHistory.count == strengthSamples,
   let oldest = strengthHistory.first, let newest = strengthHistory.last {
    steadyTone = newest.strength >= steadyRatio * oldest.strength
}

if currentMidi != nil, !passesGate,
   strongest <= presenceAmplitude || steadyTone {
    returnToIdle()
}

If the note is gone, or what’s left has stopped fading, the tuner goes back to rest.

The guitar’s own voice.

Stasis finds a note by measuring how often the sound repeats. It’s fast, it’s precise, and it can’t mistake an overtone for the note. But every so often, a soft high E would show up as an A.

It took an experiment to see why. An acoustic guitar’s body has a resonance of its own, a low “box” note, and on mine it sits right around A2, 110 Hz. High E is 330 Hz, exactly three times higher. Pluck the string softly and the two sound together, and the combined wave only repeats at the body’s pace. The detector wasn’t wrong. It was answering a different question: how often the whole sound repeats, rather than which note stands out.

So after finding the repeat, Stasis checks where the energy actually sits. A real A spreads its energy across all of its harmonics. A body note plus a high E puts nearly all of it on every third one.

// The repeat is how often the whole sound repeats, which isn't always the
// note being played. So the energy is checked too, at the repeat's harmonics.
private static func prominentNote(_ x: [Float], repeatFrequency f0: Double,
                                  sampleRate: Double) -> Double {
    var harmonic = [Double](repeating: 0, count: 9)
    for k in 1...8 { harmonic[k] = energy(at: Double(k) * f0) }
    let total = harmonic.reduce(0, +)

    // A real note spreads its energy across its harmonics. A low tone plus a
    // note at 2, 3 or 4 times it puts nearly all of it on every 2nd, 3rd or 4th.
    for m in [4, 3, 2] {
        let multiples = (2...8).filter { $0 % m == 0 }
            .map { harmonic[$0] }.reduce(0, +)
        let others = (2...8).filter { $0 % m != 0 }
            .map { harmonic[$0] }.reduce(0, +)
        if multiples > prominenceRatio * others,
           multiples > prominenceMinShare * total {
            return f0 * Double(m)
        }
    }
    return f0
}

Across more than a thousand simulated readings, from soft plucks to hard ones, with room noise and the body ringing, it named the right note every time. Tap the body on its own and it still shows an A, because that really is an A.

Rings that don’t panic.

Some strings never sit still. The open A on my guitar wobbles a few cents either way on every pluck, which kept the inner ring starting and stopping. The gauge is allowed to wobble, that’s its job. The rings should say something calmer: it’s a little wobbly, but it’s in tune.

So the rings follow the median of the last 0.4 seconds instead of every reading, and once a ring has stopped, it takes a clearly bigger error to start it again.

// The rings follow where the note sits, not every wobble:
// the median of the last 0.4 seconds of cents.
let sorted = centsHistory.map { $0.cents }.sorted()
let median = sorted[sorted.count / 2]

// A ring freezes once the median is inside its window, and only
// starts turning again when it is clearly outside it.
innerFrozen = innerFrozen
    ? abs(median) <= innerInTuneCents * freezeExitFactor
    : abs(median) <= innerInTuneCents

A wobbling note averages out to stillness, while a string that’s genuinely sharp or flat still spins the rings in the right direction.

Get Stasis.

Coming soon to iPhone, Mac and Apple Watch. No accounts, no ads and no tracking.