---
title: I cloned my voice so my blog could read itself
description: "Four voices, every post, zero dollars a month. Why a lifelong text to speech user finally built narration into his own site, and the voice clone that almost fooled me."
date: 2026-08-27
tags: [ai, design, build, narration]
url: "https://mahdif.com/blog/i-cloned-my-voice-so-my-blog-could-read-itself/"
---

# I cloned my voice so my blog could read itself

Four voices, every post, zero dollars a month. Why a lifelong text to speech user finally built narration into his own site, and the voice clone that almost fooled me.

<figure data-wide style="margin:0 0 2.75rem;"><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/blog-cover.png" alt="The listen chip with its tooltip, beside a blurred close-up of the player" width="1274" height="560" style="display:block;width:100%;height:auto;" /></figure>

Most things I read, I also listen to. I select the text and have macOS read it while my eyes follow along. I used to pay for Speechify. I dropped it when the built in Mac voices got good enough that I stopped noticing them.

Reading and listening at the same time puts me in a focus state that reading alone does not. Two channels, one text. It keeps me inside the sentence instead of three tabs away.

But my own blog could not do that for you. You would have to know the macOS trick exists, and most people have never heard of it. So now there is a listen button on this page. Press it and this post reads itself to you in four voices, and the one it starts with is mine. Every post here has audio, and so does every case study, once you have the password. It all renders on my own machine and costs zero dollars a month. This is the build log: what I picked, what surprised me, and the one part I refused to hand over.

<figure style="margin:2.75rem 0;"><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/listen-button.png" alt="The listen chip in its resting and hovered states" width="1280" height="308" style="display:block;width:100%;height:auto;" /><figcaption>The way in. The listen chip resting, and hovered with its tooltip.</figcaption></figure>

## The bake-off ran blind

I had my agents research text to speech models I could run locally for free, then put every candidate on a plain HTML page. One row per model, a shared player, a speed control, and a toggle that hides the model names so the pick could not be biased by reputation.

<figure style="margin:2.75rem 0;" data-wide><div class="browser-frame"><div class="browser-frame-bar"><span class="bf-dot"></span><span class="bf-dot"></span><span class="bf-dot"></span></div><div class="browser-frame-scroll"><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/bakeoff-lab.jpg" alt="The voice bake-off page, blind mode on" width="1225" height="1475" style="display:block;width:100%;height:auto;" /></div></div><figcaption>The bake-off lab, blind mode on. Every candidate reads the same excerpt. Scroll it.</figcaption></figure>

Two rules made the test honest. Every candidate read a long multi paragraph script, never a demo sentence, because one model kept turning into a different narrator a few seconds in and a one line sample would have hidden it completely. And before choosing anything I sat through a full seven minute post, start to finish, because a voice that charms you for two minutes can wear badly by minute six.

Three stock voices survived: Bella, Puck and Heart. All from the same small model, which I did not know until I unhid the names: [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M). 82 million parameters, open weights, free.

## The clone that almost fooled me

The fourth voice is mine. The reference is 39 seconds of me reading at my desk. It took two takes, because take one pronounced things I never would.

Here is the part that genuinely shocked me. One of the clone generations, from [Qwen3-TTS](https://github.com/QwenLM/Qwen3-TTS), was so close to my real voice that if I had heard it cold, I would have been fully convinced it was a recording of me. Not impressed. Convinced.

I did not ship that one. I shipped a slightly less identical clone from [Chatterbox](https://huggingface.co/ResembleAI/chatterbox), because it reads with more expression, and a voice can be a perfect copy and still a flat read. Realism is not the finish line. Maybe I overshot and the shipped voice is a touch too lively. I would rather err on the side of a narrator who sounds interested.

<figure style="margin:2.75rem 0;"><div class="audio-samples"><div data-audio-sample data-src="/audio/blog/i-cloned-my-voice-so-my-blog-could-read-itself/qwen3-clone.m4a" data-label="The clone that fooled me" data-note="Qwen3-TTS"></div><div data-audio-sample data-src="/audio/blog/i-cloned-my-voice-so-my-blog-could-read-itself/chatterbox-clone.m4a" data-label="The clone I shipped" data-note="Chatterbox, the voice here"></div></div><figcaption>The same paragraph, read by both clones, grown from that one recording of me. Judge the trade yourself.</figcaption></figure>

## I specced the player like a human was going to build it

The player took longer than the voices, and most of that time was mine.

I first generated five complete design directions and picked one. Then I asked for revisions, got exactly what I asked for, and liked the result less with every round. The UI was getting heavy. Each revision was correct on its own and the sum was worse than the start.

So I opened Figma and drew it myself. A floating listen button that follows you down the page, opening into a single glass row.

<figure style="margin:2.75rem 0;" data-wide><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/player.png" alt="The player bar, open in its default state" width="1280" height="260" style="display:block;width:100%;height:auto;" /><figcaption>The player. One glass row, everything within reach.</figcaption></figure>

Then I specced the frame the way I would for a human engineer: exact sizes, exact colors, red notes for behavior, both placements drawn out.

<figure style="margin:2.75rem 0;"><div class="browser-frame"><div class="browser-frame-bar"><span class="bf-dot"></span><span class="bf-dot"></span><span class="bf-dot"></span><span class="browser-frame-title">Figma spec</span></div><div class="browser-frame-scroll"><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/full-spec.png" alt="The complete annotated player spec from Figma" width="1174" height="7422" style="display:block;width:100%;height:auto;" /></div></div><figcaption>The full spec, unabridged, exactly as it was handed over. Scroll it. Red text is behavior, everything else is measured.</figcaption></figure>

I handed all of it to Claude Code. And to keep the implementation honest, it built me a small comparison dashboard: the Figma frame on one side, the live build on the other, overlaid with a slider I can drag to see exactly where they disagree. We looped until they did not.

<figure style="margin:2.75rem 0;" data-wide><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/compare-dashboard.jpg" alt="Figma design and live implementation overlaid with a comparison slider" width="2160" height="920" style="display:block;width:100%;height:auto;" /><figcaption>The comparison dashboard. Drag the slider, find the lie, fix it, repeat.</figcaption></figure>

This is the same handoff I spent 17 years giving people, except the builder answers in minutes and never gets tired of my nitpicks. Figma stayed the design surface. The code became the print.

## The detail nobody will notice

The four voices read at different speeds. The same post is seven minutes in one voice and not in another.

So the player stores your position as a fraction of the text, not as a timestamp. Switch voices halfway through a paragraph and you land on the same sentence, not the same second. Closing the bar does not stop the audio, because closing a bar is not a request for silence. Come back three days later and it remembers your place, and it does not start playing on its own. Nothing plays unless you pressed something.

Same spirit in the scrubber: the click target around the seek head is a good deal larger than the seek head you can see. Grabbing a four pixel line should not require aim.

<figure style="margin:2.75rem 0;"><img src="/images/blog/i-cloned-my-voice-so-my-blog-could-read-itself/player-states.png" alt="Three zoomed player states: play hovered, the speed tooltip, and scrubbing" width="1280" height="798" style="display:block;width:100%;height:auto;" /><figcaption>Zoomed states. Note the seek head: its hit area is larger than the control itself, so scrubbing never needs precision.</figcaption></figure>

If all of that works, you will never once think about it. That is the job.

## Where this falls short

The audio goes stale the moment I edit a post. A command lists what has drifted, and it exists because I know I will forget.

Password protected posts get no audio at all, on purpose. Their narration would be an unauthenticated copy of a private text on a guessable URL.

And the clone is good, not me. It never stumbles, never speeds up when it likes its own sentence. It is a decent way to hear my writing when your eyes are busy. It is not me reading to you.

## Sound on

I have wanted this feature for years, back when building it would have meant a vendor, a bill and a compromise on every one of these details. Instead I got the voices for free, drew the player myself, and had the implementation checked against my Figma frame pixel by pixel.

I have written before that the production moved to the machine and the design moved back to me. This feature is that sentence with the sound on. And I honestly cannot remember a version of this job that was more fun.
