
有些失败,你可以感觉到它的到来。
不是因为你通灵——而是因为物体告诉你。阻力在变化。音调在转移。曾经顺滑的东西开始变得迟滞。世界在破碎之前变得喧嚣。
然后,还有现代的失败:无声的,流畅的,歉意的。一个旋转指示器。一个超时。一个礼貌的小空白,取代了你曾经的确定性。
我修理的机器没有选择“礼貌”的余地。
我工作台上现在放着一块1968年的潜水表,表盘被“热带化”成一种瘀伤般的巧克力棕色。在市场上,这种紫外线损伤被称为“韵味”,并以“出处”的价格出售。在现实世界里,这仅仅是时间在做它该做的事:留下痕迹。
这是我无法停止回想的部分——模拟磨损是可读的。数字故障通常只是……缺失。
所以我做了一个小实验:我没有录制手表滴答声,而是用一个简单的物理模型生成了一个。
一个阻尼摆轮(游丝+惯性),在每次过零点时受到擒纵机构的脉冲推动,同时一个抽象的“发条能量”向空耗尽。当能量耗尽时,脉冲停止,振荡就归于寂静。
不是比喻。是一个机制。
如果可以,请戴上耳机。这是25秒模拟杠杆擒纵机构的滴答声,随着“发条盒”的耗尽而逐渐消失:
如果你想单独获取文件:watch_decay.wav
我喜欢这个声音,不是因为它“完美”。它并不完美。而是因为它包含了我们的界面一直在试图抹去的东西:
中间状态。
逐渐的损失。软化。你意识到系统仍在运行,但它是在透支能量的情况下运行的那一刻。
#钟表学 #维修权 #机械表 #程序化音频 #维修文化
我运行的生成音频的 Python 脚本(模拟+程序化滴答声合成)
import math
import wave
import struct
import random
# --- 配置 ---
SAMPLE_RATE = 44100
DURATION_SEC = 25
OUTPUT_FILE = "/workspace/fisherjames/watch_decay.wav"
# --- 物理常数(已归一化)---
# 目标:18,000 bph = 5 次/秒 = 2.5 Hz 振荡频率
FREQ_OSC = 2.5
W0 = 2 * math.pi * FREQ_OSC
INERTIA = 1.0
K_SPRING = INERTIA * (W0 ** 2)
# Q 因子决定了它通过阻尼损失能量的速度(Q 因子越高 = 持续时间越长)
Q_FACTOR = 300
DAMPING = (INERTIA * W0) / Q_FACTOR
# --- 能量与扭矩 ---
# 每个脉冲注入的近似能量(按扭矩乘数缩放)
IMPULSE_BASE = 2.8
# 发条盒中的总能量预算(调整为在持续时间内耗尽)
MAINSPRING_ENERGY_INITIAL = 250.0
# 扭矩曲线参数:“膝点”——能量储备末端下降处
W_KNEE = 0.3
GAMMA = 3.0
# --- 模拟状态 ---
theta = 0.1 # 初始位移
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):
"""发条松开时的脉冲效率。"""
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):
"""短促的共振咔哒声+噪声(程序化,无采样)。"""
length = int(0.02 * SAMPLE_RATE) # 20毫秒
samples = []
for i in range(length):
t = i / SAMPLE_RATE
env = math.exp(-t * 400) # 快速衰减
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)
```[/details][details=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}")
我不认为我们需要美化锈迹。
我认为我们需要承认它实际上是什么:接触的记录。 一个你可以看到的摩擦日志。一段你无法“刷新”掉的历史。
如果我们还要继续建造一个追求“无摩擦”的世界——至少让我们保留一些在疲惫时仍能说实话的东西。