声音也可以写成数学。 Sound can be written in mathematics too.

弦长决定音高,共振峰决定元音,几毫秒的错位把一段噪声变成拍手。 把这些关系写成函数,代码就能直接算出波形。下面的曲子没有使用采样包,它们都从这些公式开始。 String length sets pitch, resonances shape a vowel, and a few milliseconds of separation turn noise into a clap. Written as functions, those relationships produce the waveform directly in code. The pieces below use no sample packs; each one begins with these equations.

曲目Tracks 从公式算出波形,再离线渲染成完整曲子;频谱图也可以直接拖动 waveforms calculated from equations, then rendered offline; drag the spectrogram to seek
C 大调前奏曲Prelude in C Major, BWV 846 Prelude in C Major, BWV 846 60 BPM · C 大调C majorC 大调 / C major · 2:25

按公版原谱重建完整的 35 小节。和声始终被拆成流动的十六分音符,直到最后两小节才停下来收束。音符来自乐谱,钢琴音色和录音则由代码重新生成。 All 35 bars are rebuilt from the public-domain score. The harmony keeps moving as sixteenth notes until the final two bars bring it to rest. The notes come from the score; the piano and recording are generated anew in code.

C 大调前奏曲 / Prelude in C Major, BWV 846 的频谱图
0:00 / 2:25 开篇 · 展开 · 属音持续 · 终止 opening · expansion · dominant pedal · cadence 开篇 · 展开 · 属音持续 · 终止 自然动态natural dynamics
致爱丽丝Für Elise, WoO 59 Für Elise, WoO 59 72 BPM · A 小调A minorA 小调 / A minor · 2:19

按公版原谱重建完整的 A–B–A–C–A 回旋结构,并不只截取开头主题。A 小调主题两次离开,也两次回来;钢琴音色和录音由代码重新生成。 The complete A–B–A–C–A rondo is rebuilt from the public-domain score, not just the opening theme. The A-minor refrain leaves twice and returns twice; the piano and recording are generated anew in code.

致爱丽丝 / Für Elise, WoO 59 的频谱图
0:00 / 2:19 A 主题 · B 段 · A 再现 · C 段 · A 尾声 A refrain · B turn · A return · C storm · A coda A 主题 · B 段 · A 再现 · C 段 · A 尾声 宽动态wide dynamics
沿海公路Coast Road Coast Road 112 BPM · D 大调 / B 小调D major / B minorD 大调 / B 小调 / D major / B minor · 1:12

明亮钢琴、拨弦和木琴般的高音,用 3+3+2 的切分向前推。主题再次出现时换了落点,也换了结尾。 Bright piano, plucked strings, and bell-like highlights ride a 3+3+2 pulse. The returning theme changes both its landing points and its ending.

沿海公路 / Coast Road 的频谱图
0:00 / 1:12 轮廓 · 上路 · 晴空 · 回归 · 余晖 sketch · road · open sky · return · last light 轮廓 · 上路 · 晴空 · 回归 · 余晖 宽动态wide dynamics
风过窗边Wind Past the Window Wind Past the Window 72 BPM · C 大调 / A 小调C major / A minorC 大调 / A 小调 / C major / A minor · 1:24

钢琴与弦乐。从八小节主题出发,第二次出现时打开音域,最后只留下最初的两个音程。 Piano and strings built from an eight-bar theme. Its second statement opens the register; the ending keeps only the first two intervals.

风过窗边 / Wind Past the Window 的频谱图
0:00 / 1:24 开场 · 主题 · 展开 · 回归 opening · first theme · lift · return 开场 · 主题 · 展开 · 回归 宽动态wide dynamics
青花Porcelain Blue Porcelain Blue 84 BPM · D 大调五声音阶 / B 小调D major pentatonic / B minorD 大调五声音阶 / B 小调 / D major pentatonic / B minor · 1:23

国风 R&B,古筝和笛子落在十六分摇摆上。 Chinese-style R&B, with guzheng and flute over a swung sixteenth-note groove.

青花 / Porcelain Blue 的频谱图
0:00 / 1:23 前奏 · 主歌 · 过渡 · 副歌 · 尾声 intro · verse · pre · chorus · outro 前奏 · 主歌 · 过渡 · 副歌 · 尾声 宽动态wide dynamics
霓虹时刻Neon Hour Neon Hour 104 BPM · A 大调A majorA 大调 / A major · 1:45

舞曲流行,锯齿合成器、人声钩子和明显的侧链起伏。 Dance pop with saw synths, a vocal hook, and an audible sidechain pulse.

霓虹时刻 / Neon Hour 的频谱图
0:00 / 1:45 前奏 · 主歌一 · 过渡一 · 副歌一 · 主歌二 · 过渡二 · 副歌二 · 尾声 intro · verse1 · pre1 · chorus1 · verse2 · pre2 · chorus2 · outro 前奏 · 主歌一 · 过渡一 · 副歌一 · 主歌二 · 过渡二 · 副歌二 · 尾声 宽动态wide dynamics
起飞Launch Launch 120 BPM · A 小调A minorA 小调 / A minor · 0:57

一段留白很多的配乐,节拍之间有足够空间。 A sparse score with plenty of room between the beats.

起飞 / Launch 的频谱图
0:00 / 0:57 开场 · 推进 · 标题 · 蒙太奇 · 尾声 open · build · title · montage · outro 开场 · 推进 · 标题 · 蒙太奇 · 尾声 宽动态wide dynamics

这些声音从哪里来 Where the sounds come from

下面四段源码分别处理滤波、拨弦、人声和拍手。 These four excerpts handle filters, plucked strings, voice, and claps.

把滤波器写成闭式The filters, in closed form The filters, in closed form

core.py 第 1–13 行lines 1–13
"""Sample rate, envelopes, and every filter in the library.

The one idea worth knowing before reading anything else: **no filter in Hitone is
iterated per sample.** A one-pole lowpass and a two-pole resonator both have impulse
responses you can write down in closed form, so we write them down and convolve.

    one-pole:   h[n] = (1-a) a^n,                 a = e^(-2*pi*f/SR)
    resonator:  h[n] = r^n sin((n+1)th) / sin th, r = e^(-pi*(f/q)/SR), th = 2*pi*f/SR

Both die to -80 dB inside a few hundred samples at musical cutoffs, so the truncated
FIR is exact to well below the noise floor of a 16-bit file. This is the single reason
a full arrangement renders in seconds instead of minutes; the first version of this
library used a python `for` loop and a 100-second track took four minutes.

一阶低通和二阶共振器的冲激响应都能直接写出来。响应截到 −80 dB 以下再交给卷积函数,Python 就不用逐个采样点迭代。误差低于 16 位音频的噪声底,105 秒的曲子 8 秒就能渲完;最早那版要跑四分钟。 The impulse responses of a one-pole lowpass and a two-pole resonator can both be written directly. Truncating them below −80 dB before convolution removes the Python loop over individual samples. The error stays below the noise floor of 16-bit audio, and a 105-second track renders in 8 seconds. The first version took four minutes.

一根弦,是一条延迟线A string is a delay line A string is a delay line

osc.py 第 130–140 行lines 130–140
p = max(2, int(round(SR / freq)))
st = np.random.RandomState(int(freq * 13) % 99991)
buf = lp(st.randn(p), 400 + 7000 * bright)
buf = buf - buf.mean()
out = np.empty(n)
i = 0
for k in range(n):
    j = (i + 1) % p
    out[k] = buf[i]
    buf[i] = ((1 - damp) * buf[i] + damp * buf[j]) * decay
    i = j

先用噪声填满一条恰好一个周期长的延迟线,每循环一圈,就把相邻采样平均一次。这个平均动作就是反馈回路里的低通,所以高频比低频掉得快。拨弦的质感来自这种不均匀的衰减,普通包络做不到,因为它会把所有频率一起缩小。 Start with a delay line exactly one period long and fill it with noise. On every pass, average neighbouring samples. That average is the lowpass in the feedback loop, so high frequencies die faster than low ones. A plucked string gets its character from this uneven decay; an ordinary envelope scales every frequency together.

一个元音,是三个共振峰A vowel is three resonances A vowel is three resonances

voice.py 第 18–42 行lines 18–42
# F1/F2/F3 in Hz with relative level. Measured vowel resonances, not tuned by ear.
VOWELS = {
    'aa': [(730, 1.00), (1090, 0.50), (2440, 0.22)],   # father
    'eh': [(530, 1.00), (1840, 0.45), (2480, 0.30)],   # bed
    'ee': [(270, 1.00), (2290, 0.40), (3010, 0.28)],   # see
    'oh': [(570, 1.00), (840, 0.42), (2410, 0.14)],    # go
    'oo': [(300, 1.00), (870, 0.28), (2240, 0.10)],    # boot
    'uh': [(640, 1.00), (1190, 0.48), (2390, 0.20)],   # but
}


def voice(freq, n, vowel='aa', glide=0.0, vib=5.2, breath=0.05, q=11.0):
    """Sung vowel. Returns a mono buffer."""
    freq = nf(freq)
    fr = vibrato(bend(freq, n, glide, 0.05), rate=vib, depth=0.009, onset=0.14)
    ph = (np.cumsum(fr) / SR) % 1.0
    # A soft asymmetric pulse, not a saw. The asymmetry is what stops it sounding like
    # a filtered oscillator: a glottis snaps shut faster than it opens.
    src = np.exp(-ph * 5.5) - 0.28
    src = src - src.mean()
    out = np.zeros(n)
    for hz, lvl in VOWELS[vowel]:
        out += reso(src, hz, q, lvl)
    out += hp(lp(noise(n), 5200), 1600) * breath
    return lp(out, 6500) * env(n, 0.022, n / SR * 0.45, 1.9) * 0.5

声门脉冲先经过三个固定在元音频率上的共振器。音高变化时,共振峰留在原处,因为声道长度没有变。这样听起来更像人声,也不会变成跟着音高跑的滤波器。声源本身故意做成不对称,因为声门闭合比张开快。 A glottal pulse passes through three resonators fixed at vowel frequencies. The pitch can move while the formants stay put, because the vocal tract has not changed length. That makes the result read as a voice instead of a filter following the note. The source is asymmetric because the glottis closes faster than it opens.

一次拍手,只是时间A clap is only timing A clap is only timing

drums.py 第 32–44 行lines 32–44
def clap():
    """Four noise bursts at 0/8/17/25 ms, then a tail.

    That stagger IS the clap. One burst is a snare; a smear of them is a room full of
    hands, because no two people in a room hit on the same millisecond.
    """
    n = at(0.34)
    s = np.zeros(n)
    for d, a in ((0.000, 1.00), (0.008, 0.85), (0.017, 0.70), (0.025, 0.55)):
        k, m = at(d), at(0.02)
        s[k:k + m] += hp(lp(noise(m), 7500), 1100)[:m] * np.linspace(1, 0.2, m) * a
    tail = hp(lp(noise(n), 6200), 1300) * env(n, 0.026, 0.30, 2.6) * 0.5
    return (s + tail) * 0.9

这里用了四段噪声,分别在 0、8、17 和 25 毫秒到达。材料和军鼓相同,差别只在时间。把四个时间压到同一点,听起来更像军鼓;稍微错开,才像一群人同时拍手。 Four noise bursts arrive at 0, 8, 17, and 25 ms. The material is the same as a snare; only the timing changes. Collapse the four arrivals into one and it sounds like a snare. Stagger them and it becomes a group of people clapping together.

声音 sound 声音 / sound waveforms generated in code, without sample packs