Dial Tone
Making a Weird Little Music Game for iOS
This is a design and development journal about noodling my way to a finished game. If you just want to see the result you can jump ahead.
I promise I have nothing to sell you here. Dial Tone is free on the app store, with no ads or in-app purchases. I made it because I keep getting the itch to make the same weird little tactile music game over and over. My last attempt (also free) was Khromax.
They are not, strictly speaking, the same. But if you try both you’ll spot the common DNA. The idea I keep coming back to is:
What if Slide to Unlock was a Game?
If you were one of the early adopters of a first gen iPhone, you remember sitting there locking and unlocking the thing over and over.
Sure, technically, it turned out to be a horrible way to secure our phones. But there’s a reason it, out of all the things in a packed demo, got such a reaction from the crowd. There was something there. It was fun.
A lot of iOS games (including some I love) fall into two broad UX categories:
- Awkwardly porting desktop or console inputs to your phone. Think virtual d-pads on smooth glass.
- Everything is tap.
We can do better! Slide to Unlock was so fun because it felt tactile. Pixels magically transmuted into physical objects. Mobile games should embrace the medium.
What even is a game
I should be clear. When I say “game” I mean a tight loop of fun mechanics. Not an interactive story. If you imagine a spectrum all the way from Tetris to Skyrim, Dial Tone is far on the Tetris side.
(What’s in the exact center?)
An advantage of the Tetris end of the scale is that you can start with a single, stupidly simple idea and iterate, playtest, and polish your way to a game. One can imagine starting with “stacking blocks” and chipping away everything that isn’t fun until you arrive at Tetris. It’s harder to start with “what if dragons?” and end up at Skyrim.
How about a circle
What’s better than slide to unlock? Infinite slide to unlock! I wanted the core mechanic to be a rotating gesture, so my first test build was just a ring on a screen. To capture that magical illusion of physicality, I put a lot of effort into tuning the inertia just right for a satisfyingly flickable wheel.
That was a complete waste of time. It was a neat prototype, but a terrible game, as you’ll soon see.
How about a difficult circle
The obvious way to gamify a rotating ring is some kind of orientation challenge. I considered both avoiding projectiles and aligning multiple rings. Both were moderately fun on their own.
Combining them was better. Switching from avoiding projectiles to collecting them, each ring became a growing obstacle for the others.
I tried several approaches for the projectiles. At first they were spawned from the center of the ring, since this gave me a full 360° in which they could randomly fire. But on a tiny phone screen, this meant they had to travel agonizingly slowly for the player to have any time to react. It played acceptably frantic, but it didn’t look right.
Next I tried firing from the screen corners. Travel time felt good. But it was hard to distinguish which ring a projectile was meant for, especially for color blind players.
Then I tried making the projectiles into segments of the rings themselves. That meant there was a second way to distinguish them, by relative size, and it made clear from the start the mechanic was this will attach to the corresponding ring. But I had to either make gaps in outer rings large enough for inner segments to pass through, which made the game less challenging, or make the projectiles into miniature segments which defeated the legibility.
The answer was to forget about projectiles. I switched to lasers. Thin filaments extending from the corners of the screen to specific rings. This had several advantages: it made targeting clear, it made the path of collisions clear, and by spawning the next filament while the current one was traveling, I gave the player the ability to think one move ahead, turning the game from purely reactive to predictive. The first hints of strategy.
For the music phreaks
While fiddling with concentric rings I’d divided them into 4, 8, and 16 segments out of habit. And then I noticed ... hey, those could be divisions of a beat. Quarter notes, eighths, and sixteenths.
I assigned each ring a note, with a simple hardcoded chord progression, and added a “minute hand” that swept over the rings, playing the appropriate note whenever it hit a segment. A happy accident of separating the minute hand (which rotated in fixed world space) from the rotation of the rings meant that I didn’t just have chords, I had a little emergent melody that changed as the player dialed the rings.
I broke the minute hand up into individual trackers for each ring (I call them sweeps), and tweaked the audio trigger so instead of sounding a single distinct note, they made a sustained tone for as long as they remained in contact with a segment. This means rows of segments sound different from lone segments, a ring in motion sounds different than at rest, and a skilled player can even play a non-stop tone by chasing the sweep around the ring. A tiny, ridiculous musical instrument.
I then tried assigning two notes to each ring, for a six-note chord. That made individual segments sound a little like the keys of a touch-tone telephone. And here I had rings you dialed. The game had a name.
I’ve been fascinated by procedural music for even longer than I’ve been obsessed with Slide to Unlock, and this was a perfect opportunity. I replaced the hardcoded chord progression with a procedural music generator.
At the start of each game it randomly picks from seven modal flavors, seven banks of percussion, and a starting key. It then generates tension and resolution with a state machine that cycles through tonic → predominant → dominant → back to tonic on ring placements. As BPM increases, the pull toward dominant and back to tonic gets stronger, creating shorter, more urgent phrases. Whole phrases also have a chance to be repeated or returned to in the next key, reinforcing a sense of musical structure.
Combined with placement and orientation creating different polyrhythms between rings, every game is a unique composition. The overall impression is something recognizably musical but a little uncanny. The RNG makes choices a human composer — or a sophisticated AI composer aping a human — wouldn’t. I think this gives Dial Tone a distinctive sound, and captures a little of the weird charm of pre-LLM machine “creativity”, like the found poetry feel of Markov text.
I set the travel time for new lasers at 1.5 bars, creating a hemiola. The beat completes one revolution per four beat bar. A laser every six beats means arrivals alternate between beat one and three. The full placement pattern only repeats every three bars. The player hears a long-form phrase with built-in syncopation.
It was pretty catchy. But something still felt missing. Leaning into the eerie synthesizer sound, I added a portamento, a smooth slide between pitches. It fit the rotation theme. As if the ring was a knob and you were tuning in to each new chord.
Inertial dampeners
Remember how I spent all that time getting the ring inertia just right? Now that I had something genuinely fun to play, and was starting to get good at my own game, I discovered inertia was actually maddening. Losing because your finger imparted a little drift to the ring as you left the surface of the screen sucked.
The solution was easy: throw that work away. I’d built a game of precision. The rings needed to feel like precision dials, stopping just where you put them. Not well oiled bearings.
I actually tried making them clicky, snapping to precise alignments with a little haptic tick. It felt neat, but made the game too easy. With a lot of playtesting, I felt faithfully accurate but punishingly precise was the sweet spot.
Accelerando
Speaking of difficulty, it was time to start tuning the curve. I wanted Dial Tone to feel slightly frantic but approachable to new players, leading into a rapid phase of initial mastery (conquering a new skill is fun), and then a long, ideally infinite tail of obscene difficulty for expert players to throw themselves against.
Having already synced the game to a 4/4 beat, the obvious mechanic was a ramping BPM. And there’s something really appealing about a concrete mechanic replacing abstract “points”.
(Shout out to Canabalt, the first iOS game I ever fell in love with, which made the brilliant decision to score by distance.)
120 BPM felt like a good starting zone. But there was a problem. I’d been defining a “level” as filling all three rings completely, but at 120 that took a while. I wanted Dial Tone to feel fast-paced, even at low levels. I changed the win condition to clearing any single ring.
Which introduced its own problem: luck. With every ring having an equal chance to spawn, it was entirely possible to clear an arbitrarily high level just by trying over and over until you got favorable starting conditions. In the “worst” case you might start with lasers from all four corners completely filling the inner ring without having to lift a finger.
A little luck can feel good. I wanted to leave some room for it. But I also wanted leaderboards. Scoring had to mean something!
I could have just programmatically forbidden the most blatant case of instant-clear, but I went further with several tweaks across the leveling experience:
- Spawns are random but weighted by ring size. Outer rings are more likely to fill up first, creating more obstacles.
- To avoid the lucky case where you simply fill the outer ring without having to maneuver inner rings at all, spawns are also weighted by ring fullness. As the outer ring fills, and becomes a major obstacle, the inner rings become more likely to spawn.
- These are all just weighted probabilities. There’s no hard rule that you can’t get lucky. In fact I want it to happen now and then.
- When you first launch the game you default to 120 BPM, but you can choose to start as low as 90 or as high as 200.
- Past 200, you can start a new session at your best score minus three. Skilled players don’t have to start from the beginning, but you can’t just luck your way to an arbitrarily high score. Anyone on the leaderboard above 200 earned it by surviving at least three rings at that level.
The big reveal
And now, at last, I had a real game.
And, emergently, a soundtrack.
Secret strategy
You might think a game with exactly one mechanic wouldn’t have a lot of room for “strategy”, but in playtesting a few naturally emerged.
I’m putting these behind a spoiler warning for those who prefer to discover on their own. Feel free to go play the game now and come back to see if your findings match mine.
For the impatiently curious, click to reveal.
The simplest case is purely reactive: every time a laser spawns, perform the minimum rotations that will avoid losing. But can you do better? Can you put in more effort up front, rotating more than strictly necessary while rings are sparse, in order to create better conditions for yourself as they fill up?
Turns out yes. I discovered two dominant and opposing strategies:
-
Maximize the number of gaps on each ring. In the simplest case, this means starting from a bare ring: let the first segment place randomly, then place the second 180° opposite, dividing the ring into two equal gaps. As the rings fill up and become more dangerous, this optimizes for minimal rotation to avoid a loss. You’re more likely to have a gap near every spawn point, although you’ll have to be precise.
-
Create and sustain one big gap. Place each new segment touching the last. As the rings fill up, this means bigger rotations, but also a longer period with a large and very forgiving gap to pass through. More speed, less precision.
Of course, they both eventually result in a nearly full ring with only a single segment gap. The difference is in the mid-game, where you may find one or the other strategy more physically or mentally sustainable, or more conducive to a flow state.
They also sound different!
I should also mention that Dial Tone is fully multitouch. Nothing is stopping you from turning two or even three rings at once, and mastering this might add a couple hundred BPM to your score. But I don’t have any more to say on the subject because when I tried it I was forced to confront my own mortality. It feels like one of those insane techniques for young people with impossibly fast reflexes and I leave exploiting it in their capable hands.
Demo mode
I knew I’d eventually need to record gameplay footage for the app store (and this blog!), so from the start I baked a scriptable demo mode into the game.
A demo script looks like this:
{
"bpm": 200,
"color": 2,
"flavor": 1,
"drums": 2,
"rings": {
"inner": [0, 2],
"middle": [0, 2, 4, 6],
"outer": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
},
"moves": [
{ "ring": 2, "corner": 0 },
{ "ring": 2, "corner": 3 },
{ "ring": 2, "corner": 1 },
{ "ring": 1, "corner": 2 },
{ "ring": 1, "corner": 1 },
{ "ring": 1, "corner": 3 },
{ "ring": 1, "corner": 0 }
]
}
This creates a game with mostly full rings and proceeds to fire the defined lasers, intercepted by the defined rings, until the middle ring clears. After the scripted moves are exhausted, the game enters normal random spawning and plays itself according to strategy 1 in the spoilered section above.
I built it to capture repeatable video without the pressure of playing a perfect game, but it also turned out to be great for testing and QA.
Procedural everything
Dial Tone has no image files, no audio files, no font files, no data files. The bundle contains only compiled Swift code. Everything is generated at runtime:
- Rings and segments:
SKShapeNodegeometry - Background, segment fill, lasers: GLSL shaders
-
Laser spark:
SKEmitterNodewith a programmatic 4x4 texture -
Soundtrack:
AVAudioSourceNoderender callbacks (oscillators, noise, envelopes) -
Typography: system-installed Avenir Next with a custom
CGPathtransform for curved text
Even the app icon, while necessarily static, is generated by a tiny Swift utility using the game code.
I did this for a few reasons. It let me build the responsive soundtrack that became integral to the whole feel of the game. It made the development and build pipeline extremely simple, which let me spend more time playtesting and iterating, and I think that shows in the final product. I’m a programmer, not an artist, so that left math art, which fits my sensibilities anyway.
And it’s pretty cool, in 2026, to ship a game under 6MB.
Player perspective
By the time I was ready for external playtesters I really needed them. I’d played hours upon hours of my own game and gotten too close to the work. As the undisputed Dial Tone World Champion, I’d started thinking of easy difficulty as too easy and was considering cranking it up. Watching fresh new players struggle to keep up brought me back to reality.
But it was worse than that. I wanted Dial Tone to be discoverable, so I didn’t provide any instructions. I just sat people down with a game they’d never seen before and asked them to figure it out. And I was shocked to find that, on first playthrough, nobody tried turning the rings. Not a single person. They all tried tapping and were confused when it didn’t do anything. I didn’t provide any hints, and they all figured it out after a loss or two, so it wasn’t completely undiscoverable. But it wasn’t obvious.
Right. I knew I’d set out to build a game that was “more than just tapping”. But the players didn’t know that. And the game did nothing to tell them.
After figuring it out, one tester said incredulously, “but the instructions said to tap!”
Instructions? I hadn’t provided any instructions...
At this point all the development had gone into the game loop itself. The start menu was an afterthought. Just a way to get to the game. It looked like this:
Tap to play. Oops. Now it looks like this:
The launch controls tell you how to play the game. No tutorial mode necessary. And now the start menu itself is fun.
I will say this for getting too close to the work: I sanded down a lot of rough edges, and added a lot of polish, just from playing the game obsessively and fixing every tiny little thing that started to annoy me after the thousandth game. The optimal amount of obsession is not zero. But stop before you overseason it.
Dial Tone for everyone
As I mentioned earlier I wanted to be sure Dial Tone would work for color blind players. I did two things to make the game playable without any color information:
-
Added a collision warning animation. Endangered segments enlarge and flash translucent. Almost everything else in the game is synced to the beat, so to make the warning extra hard to miss I intentionally desynchronized it.
-
Played the game without color. iOS makes this really easy: Settings → Accessibility → Display & Text Size → Color Filters → Grayscale
I also added guards to all the non-essential animation to honor iOS’s built-in accessibility settings for reduced motion and sensitivity to flashing lights. When either setting is active I also further enlarge the collision warnings to make up for the missing flash.
I went back and forth on how to handle device mute. Audio is a major part of the Dial Tone experience, but it’s not strictly necessary to play, and there are legitimate reasons a player might not want it to make noise.
What I really wanted was to honor silence but show some kind of warning icon that you were missing out on the soundtrack. But Apple very intentionally does not expose any API for determining mute state. There are some libraries that get around this with very clever hacks. But I try to honor the spirit of Cupertino Law, not just the letter.
So if you mute your iPhone, Dial Tone will respectfully not make a sound. I’m not saying you should play Dial Tone in class, or during boring meetings. I’m just saying I won’t rat you out if you do.
That’s it.
Thanks for reading. I’m bad at wrapping things up which is why my games just keep getting harder forever.





