Play a recording at 1.5x by reading the samples faster and every frequency in it goes up by 1.5. A voice at 100 Hz lands at 150 Hz. That is 12 x log2(1.5) = 7 semitones, a perfect fifth. Chipmunks. This is resampling, and it ties speed to pitch. Nothing inside it lets you pull them apart.
To separate them, stop treating audio as a stream of samples and treat it as a train of short pieces. Cut the signal into overlapping frames a few tens of milliseconds long. To go 1.5x, read frames from the source at 1.5x spacing and lay them into the output at normal spacing. Each frame plays at its original rate, so its pitch does not move. You have skipped a third of the audio, and speech survives that because it repeats itself. A vowel is one pitch cycle over and over. Drop some cycles and you hear nothing missing.
The catch is the seam. If you copy the next frame from exactly where the arithmetic says, its waveform starts at some random point in the pitch cycle. The two overlapping frames disagree about where the wave is, partly cancel, and you hear a warble. Plain overlap-add (OLA) sounds like that.
WSOLA fixes the seam. Waveform Similarity Overlap-Add (Verhelst and Roelands, 1993) does not take the next frame at the exact position. It searches a few milliseconds either side and picks the offset whose waveform best continues the audio already written. Best means highest cross-correlation with what would naturally have followed the previous frame. Then it cross-fades with a Hann window.
offset = the d in [-D, +D] that maximises
corr( source at (t + d), natural continuation of last frame )
output = hann x source at (t + offset), added at the normal hop The seam now joins wave to wave, in phase. The cost is one small cross-correlation per frame, cheap enough to run live behind a decoder. That is how Attix speeds audio up.
Where it breaks. Consonants like p, t and k are short and not periodic, so a frame can repeat or drop one and you get a stutter. Music with several notes at once has no single period to match. Past about 2x you are deleting whole syllables rather than cycles, and no algorithm fixes that. The other common method, the phase vocoder, works in the frequency domain. It copes with music better, but it smears sharp onsets and adds a metallic phasiness that speech shows up immediately.
On headphones a mono voice sits in the middle of your head. Both ears get the same signal at the same instant, so your brain finds no direction and puts the source in the only place left. A real voice in a room differs between your ears in three ways. It reaches one ear first, by up to about 0.6 ms across a human head. It is louder at the near ear, because the head shadows the far one. And the outer ear colours the sound differently for each direction, mostly above a few kHz.
All three cues are one thing: a filter. Put a speaker at some angle, play a click, record it with a small microphone in each ear canal. You get two short impulse responses, one per ear, called HRIRs. Convolve a mono voice with the left one for the left ear and the right one for the right ear, and the voice seems to come from that angle. It is arithmetic, nothing else.
Someone already measured a head. KEMAR is a mannequin with a standard head, torso and ears. In 1994 Bill Gardner and Keith Martin at the MIT Media Lab measured it from hundreds of directions and released the data free. Each direction is just those two short filters, so the set is tiny. Attix carries it inside the binary in 38 KB.
The runtime cost is multiply-adds. Direct convolution with an N-tap filter costs N multiplies per output sample, per ear. Take 128 taps at 44,100 Hz as an example: 128 x 44,100 x 2 is about 11.3 million multiply-adds a second. A modern core does billions. The soundstage in Attix runs at 1.2% of one core.
Then you choose a seat. Put the voice in front and a little to one side, where a desk speaker would be, and the brain stops looking for it inside the skull.
Limits. Your head is not KEMAR's. Ear shape drives the direction cues, so a stock set gets left and right correct but can get front, back and height wrong for some listeners. A voice meant to be ahead can sound behind. It only works on headphones, because speakers add their own crosstalk between the ears. And a dry HRIR gives direction more than distance. Real distance cues come from the room, so a little reverb helps the voice sit outside you.
Keyword search fails the moment people use different words. "Acquiring another company" shares nothing with "how big was the team that got acquired", and transcripts are full of people saying things sideways. Embeddings fix that.
An embedding model turns a piece of text into a list of numbers so that texts with similar meaning land close together. BGE-small is a small one: about 33 million parameters, 384 numbers out per text. That is small enough to run on a laptop CPU without a GPU, which is what running in-process needs.
Search is then three steps. Cut the transcript into chunks of a few sentences and keep each chunk's start time. Embed every chunk once. At query time embed the query the same way and rank chunks by cosine similarity. If the vectors are normalised to length 1, cosine similarity is just a dot product. The best chunk has a timestamp, so a hit can start playing from that second.
chunks -> embed once -> vectors, 384 floats each
query -> embed -> dot product against every vector -> top hit -> start time
You do not need a vector database. Do the arithmetic. 384 floats at 4 bytes is 1,536 bytes per chunk, so 10,000 chunks is about 15 MB. Scanning all of them for one query is 10,000 x 384 = 3.8 million multiply-adds, a few milliseconds. Brute force is the right algorithm until you have millions of chunks. Approximate indexes like HNSW solve a scale a podcast app does not have.
Running the model inside the binary is the part that matters. No server to start, no API key, and the text you search never leaves the machine. The real cost is the model file, which has to ship or download once. Embedding is much slower than searching, so embed once and keep the vectors.
Limits. The model reads at most 512 tokens at a time, so long text needs chunking anyway. It is strong on topic and loose on exact strings. A name, a number or a version string wants plain keyword matching, so use both when you can. One more detail: BGE models were trained with a short instruction in front of the query, not the passages. Leave it off and you lose a little accuracy.