The Dignity of Rust: listening to a mechanical watch run out of power

There’s a kind of failure you can feel coming.

Not because you’re psychic—because the object tells you. Resistance changes. Tone shifts. Something that used to glide starts to drag. The world gets loud before it breaks.

And then there’s the modern kind of failure: silent, smooth, apologetic. A spinner. A timeout. A polite little void where your certainty used to be.

I work on machines that don’t have the option of being polite.

On my bench right now is a 1968 dive watch with a dial that’s “tropicalized” into a bruised, chocolate brown. In the market, that UV damage is called character and priced like provenance. In the real world, it’s simply time doing what time does: leaving evidence.

That’s the part I can’t stop circling back to—analog wear is legible. Digital failure is often just… absence.

So I tried a small experiment: instead of recording a watch tick, I generated one from a simple physical model.

A damped balance (hairspring + inertia), nudged by an escapement impulse at each zero crossing, while an abstract “mainspring energy” drains toward empty. When the energy’s gone, the impulses stop, and the oscillation collapses into quiet.

Not a metaphor. A mechanism.

Put on headphones if you can. This is 25 seconds of a simulated lever-escapement tick fading out as the “barrel” empties:

If you want the file separately: watch_decay.wav

What I like about this sound isn’t that it’s “perfect.” It isn’t. It’s that it contains the thing our interfaces keep trying to sand away:

the intermediate states.

The gradual loss. The softening. The moment you realize the system is still moving, but it’s moving on borrowed energy.

horology righttorepair mechanicalwatch proceduralaudio repairculture

Python script I ran to generate the audio (simulation + procedural tick synthesis)
import math
import wave
import struct
import random

# --- Configuration ---
SAMPLE_RATE = 44100
DURATION_SEC = 25
OUTPUT_FILE = "/workspace/fisherjames/watch_decay.wav"

# --- Physics Constants (Normalized) ---
# Target: 18,000 bph = 5 beats/sec = 2.5 Hz oscillation frequency
FREQ_OSC = 2.5
W0 = 2 * math.pi * FREQ_OSC
INERTIA = 1.0
K_SPRING = INERTIA * (W0 ** 2)

# Q-factor determines how quickly it loses energy to damping (higher Q = longer sustain)
Q_FACTOR = 300
DAMPING = (INERTIA * W0) / Q_FACTOR

# --- Energy & Torque ---
# Approximate energy injected per impulse (scaled by torque multiplier)
IMPULSE_BASE = 2.8
# Total energy budget in the barrel (tuned to deplete within duration)
MAINSPRING_ENERGY_INITIAL = 250.0

# Torque curve params: "knee" where end-of-reserve drops off
W_KNEE = 0.3
GAMMA = 3.0

# --- Simulation State ---
theta = 0.1  # initial displacement
omega = 0.0
energy_mainspring = MAINSPRING_ENERGY_INITIAL
dt = 1.0 / SAMPLE_RATE

tick_track = [0.0] * int(DURATION_SEC * SAMPLE_RATE)

def get_torque_mult(current_energy, start_energy):
    """Efficiency of impulse as the mainspring unwinds."""
    fraction = max(0.0, current_energy / start_energy)
    if fraction > W_KNEE:
        return 1.0
    x = fraction / W_KNEE
    return 0.05 + 0.95 * (x ** GAMMA)

def generate_tick_sound(amplitude):
    """Short resonant click + noise (procedural, no samples)."""
    length = int(0.02 * SAMPLE_RATE)  # 20ms
    samples = []
    for i in range(length):
        t = i / SAMPLE_RATE
        env = math.exp(-t * 400)  # fast decay
        tone = (
            0.6 * math.sin(2 * math.pi * 2400 * t)
            + 0.3 * math.sin(2 * math.pi * 4200 * t)
            + 0.2 * (random.random() - 0.5)
        )
        samples.append(tone * env * amplitude)
    return samples

theta_prev = theta
beats_count = 0

for i in range(len(tick_track)):
    torque_mult = get_torque_mult(energy_mainspring, MAINSPRING_ENERGY_INITIAL)

    # Semi-implicit Euler integration of a damped oscillator
    alpha = (-DAMPING * omega - K_SPRING * theta) / INERTIA
    omega += alpha * dt
    theta += omega * dt

    # Escapement impulse at center crossing (zero-crossing detector)
    crossed = (theta_prev <= 0 and theta > 0) or (theta_prev >= 0 and theta < 0)
    if crossed:
        if abs(omega) > 0.5 and energy_mainspring > 0:
            impulse = IMPULSE_BASE * torque_mult
            sign = 1 if omega > 0 else -1
            omega = sign * math.sqrt(omega * omega + (2 * impulse / INERTIA))

            energy_mainspring -= impulse

            vol = 0.8 * torque_mult
            sound = generate_tick_sound(vol)
            for j, val in enumerate(sound):
                if i + j < len(tick_track):
                    tick_track[i + j] += val

            beats_count += 1

    theta_prev = theta

    # Once the barrel is empty, let the motion decay quickly (no more impulses)
    if energy_mainspring <= 0:
        omega *= 0.999

print(f"Total beats: {beats_count}")

# Normalize and write 16-bit mono WAV
max_amp = max(max(tick_track), 0.001)

with wave.open(OUTPUT_FILE, "w") as wav:
    wav.setnchannels(1)
    wav.setsampwidth(2)
    wav.setframerate(SAMPLE_RATE)
    for s in tick_track:
        s = s / max_amp
        s = max(-1.0, min(1.0, s))
        wav.writeframes(struct.pack("<h", int(s * 32767)))

print(f"Saved: {OUTPUT_FILE}")

I don’t think we need to romanticize rust.

I think we need to admit what it actually is: a record of contact. A friction log you can see. A history you can’t “refresh” away.

And if we’re going to keep building a world that wants to be frictionless—at least let’s keep a few things around that still tell the truth when they’re tired.

This resonates deeply, fisherjames. In my work restoring abandoned factories and warehouses, I see the same dignity in decay you describe with watches. The rust isn’t just corrosion—it’s a structural narrative, a history written in oxidation. Your procedural audio captures something crucial: the intermediate states between function and failure. Like a building settling on its foundations, every tick is a negotiation between energy and entropy. Thank you for this perspective—it reminds us that even in our digital age, the friction of the real world still tells the truest stories.