ARTICLE · 1288680
白噪音最该信的,不是 App,是你自己造的
> 第七篇 · 动手造一段声音
上篇让你拿那段 1kHz 校准音,估一估房间里的分贝。这期顺着它往下讲,它到底是谁造的,怎么造的,以及为什么你其实可以自己造一个。
先说结论,免得你滑到末尾。
你手机 App 里那些“深度白噪音”“高级棕噪音”,背后大概率是同一段录音改了个名字。
但本号之前发的那三条不是,它们是按频谱定义一段一段算出来的。
这篇把那段代码,原样交给你。
你可能会想,自己造一段声音,跟睡得好不好,到底有什么关系?
关系是有的。
第六篇讲过,机器和 App 的默认档,往往比你该听的音量响得多。你如果能自己造一段、并且知道它的电平,就不用去猜那个档位到底几十分贝。
白噪音、粉红噪音、棕噪音,这三个词第三篇就出现过,当时只讲了它们听感差在哪儿。
这期补上它们是怎么来的。
01 三种噪音,差在频谱形状,不在名字多玄
白噪音,是所有频率一样响。数学上,它就是一串彼此独立的随机数。
粉红噪音,是白噪音过了道整形,每翻一倍频率,能量掉 3 分贝,听感比白噪音柔,像稳定下着的雨。
棕噪音掉得更狠,每倍频程掉 6 分贝,低频堆得多,像远处的雷,像空调外机。
区别不是哪种更高级,是频谱的形状不同。三种噪音像三种粗细不同的砂,白的是面粉,粉的是细砂,棕的是粗砂,撒在同一块地上分布不一样,但都是砂。
名字会骗人,波形不会。
本号测过一条标着“白噪音”的现成素材,实测斜率是 -7.31 dB/oct,那是棕噪音的频谱。波形不认广告话术,它只认物理。
所以与其盯着 App 标的名字,不如自己扫一眼斜率。一张表,把三种噪音的长相摆清楚。
02 核心就四样:随机、整形、积分、正弦
代码不长,拆成四块讲,每块对应一种声音。
白噪音最简单,每个采样点取一个均匀分布的随机数,它天然频谱平坦,因为前后两个采样互不相干。
粉红噪音,是在白噪音外面套一道滤波器。工程上常用 Paul Kellet 在 1999 年给出的近似做法,用六个不同时间常数的环节去累加、去抵消,把高频一点点压下去。你不用懂每个系数的来历,只要知道它在做一件事,把能量从高频搬到低频。
棕噪音更简单,是白噪音的积分。把随机数一个一个累加起来,低频自然就堆起来了,代价是能量会越积越大,所以要配一个很小的漏,让它别跑飞。
校准音最简单,一个 1kHz 的正弦波,sin(2π × 1000 × t)。它存在的意义不是好听,是给你一个已知频率的基准,用来标定手机麦克风,或者对照分贝表,第四篇讲过的那段就是这个。
这四种,都是用纯标准库就能写出来的东西,不依赖任何第三方包。白噪音管随机,粉红管整形,棕噪音管积分,校准音管基准,各司其职。
零依赖,意味着你不用为装包折腾,开箱即跑。
代码顶部那几个大写常量就是旋钮。SR 是采样率,44100 即 CD 音质;DURATION 是每段时长,10 秒够试听;PEAK 是峰值音量,管响度。它们都在文件最上面,改完存盘再跑就行。
看不懂也别怕,把 PEAK 从 0.2 调成 0.1,导出来的声音就轻一半。
往下看,代码在下面。
03 完整代码,和七个会坑你的细节
下面这段,存成 generate_noise.py,装了 Python 就能跑,它会吐出四个文件:white.wav、pink.wav、brown.wav、cal_1kHz.wav。
三步跑起来:一,电脑装好 Python,官网下载时勾选“添加到 PATH”;二,把下面代码存成 generate_noise.py;三,命令行进到那个文件夹,敲 python generate_noise.py,十秒后四个文件就躺在那了。
# -*- coding: utf-8 -*- """ generate_noise.py — 生成白噪音 / 粉红噪音 / 棕噪音 / 1kHz 校准音 纯标准库,零依赖。装好 Python 直接跑: python generate_noise.py 会生成四个 WAV:white.wav pink.wav brown.wav cal_1kHz.wav """ import wave import struct import math import random SR = 44100 # 采样率,CD 音质 DURATION = 10 # 每种噪音时长(秒) CAL_DURATION = 5 # 校准音时长(秒) PEAK = 0.2 # 峰值振幅 = 0.2,约 -14 dBFS,留足安全余量 CAL_FREQ = 1000 # 校准音频率(赫兹) SEED = 20260607 # 固定随机种子,保证可复现 def write_wav(path, samples): """samples: 浮点列表,范围约 [-1, 1],写入 16-bit 单声道 WAV。""" with wave.open(path, 'w') as w: w.setnchannels(1) w.setsampwidth(2) # 16 bit w.setframerate(SR) fade = int(0.05 * SR) # 50 毫秒淡入淡出,去掉首尾咔哒声 n = len(samples) out = [] for i, s in enumerate(samples): if i < fade: s *= i / fade elif i > n - fade: s *= (n - i) / fade v = int(max(-1.0, min(1.0, s)) * 32767) out.append(struct.pack('<h', v)) w.writeframes(b''.join(out)) print('已生成', path) def normalize(samples, peak=PEAK): """把峰值压到目标振幅,避免削波爆音。""" m = max(abs(s) for s in samples) or 1.0 return [s / m * peak for s in samples] def white_noise(n): """白噪音:每个采样点彼此独立、均匀随机。频谱是一条平线。""" return [random.uniform(-1.0, 1.0) for _ in range(n)] def pink_noise(n): """粉红噪音:在白噪音上做 1/f 频谱整形(Paul Kellet 近似滤波器)。 六个不同时间常数的环节累加再抵消,把高频一点点压下去。""" b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0 out = [] for x in (random.uniform(-1.0, 1.0) for _ in range(n)): b0 = 0.99886 * b0 + x * 0.0555179 b1 = 0.99332 * b1 + x * 0.0750759 b2 = 0.96900 * b2 + x * 0.1538520 b3 = 0.86650 * b3 + x * 0.3104856 b4 = 0.55000 * b4 + x * 0.5329522 b5 = -0.7616 * b5 - x * 0.0168980 out.append((b0 + b1 + b2 + b3 + b4 + b5 + b6 + x * 0.5362) * 0.11) b6 = x * 0.115926 return out def brown_noise(n): """棕噪音:白噪音的累加(积分),能量更多压在低频。 配一个很小的「漏」防止数值越积越大跑飞。""" out = [] last = 0.0 for x in (random.uniform(-1.0, 1.0) for _ in range(n)): last = (last + 0.02 * x) / 1.02 out.append(last) return out def calibration_tone(n, freq=CAL_FREQ): """1kHz 校准音:单一频率正弦波。是尺子,不是枕头。""" return [math.sin(2 * math.pi * freq * i / SR) for i in range(n)] if __name__ == '__main__': random.seed(SEED) n = SR * DURATION write_wav('white.wav', normalize(white_noise(n))) write_wav('pink.wav', normalize(pink_noise(n))) write_wav('brown.wav', normalize(brown_noise(n))) write_wav('cal_1kHz.wav', normalize(calibration_tone(SR * CAL_DURATION)))几个容易踩的坑,单列出来。
第一,振幅别给满。采样值是 -1 到 1,你真填 1,声音会削顶,出来的不是噪音是失真,还刺耳。
代码默认压到 0.2,对应大约 -14 dBFS。提醒一句,dBFS 是文件里的相对电平,不是你耳朵听到的分贝,真到耳朵还得看你手机放多大、离多远,所以第六篇那句先调小再打开,对这段也成立。
第二,粉红和棕噪音能量压在低频,听着会比白噪音闷,也更实,别因为它们不刺耳就开更大。
第三,棕噪音是积分来的,数值范围小,要记得归一化,不然导出来几乎听不见。代码里统一做了峰值归一,四种声音响度拉到一致。
第四,想要结果可复现,给随机数定个种子,不然每次跑出来波形都不一样,做对照就乱了。
第五,导出来的是 WAV,无损,适合当基准,真要发到手机上当助眠,再转成你习惯的格式就行,转的时候别顺手把音量拉高,前面定的电平就白费了。
第六,同一段文件,耳机贴耳和床头外放是两个世界。耳机可能过响,外放可能不够,亲自用第六篇的办法估一下再定。
第七,那段校准音是尺子,不是枕头。它的本职是标定麦克风、对照分贝,别拿它当助眠音整夜放着听。
淡入淡出那 50 毫秒,是为了去掉首尾的咔哒声,听着不突兀,别手痒把它删了。
给婴儿用更要注意,距离大于 2 米、音量低于 50 分贝、不要整夜播。
04 你以为买 App 最省事,其实自己造更靠谱
绕了一圈,回到开头那个结论。
你可能想问,既然 App 里都有,为什么还要自己造?
App 默认档过响,第六篇那项 2024 年睡眠医学的研究已经量过了。
研究量的是机器和 App 的最大档,不是你该用的档。
24 台机器加 6 个 App,最大档全过 85 分贝,App 默认大多在 75 分贝上下。
75 分贝是什么概念。差不多是城市主干道贴着窗的量级,整夜放床头,剂量按小时累计。
你下载一个,等于默认接受了厂家推到最大的那一档。自己造,你能把电平钉死在你想让它待的地方,而且自己造你才知道它每一格频率是多少,不用听厂家的一面之词。
你要的只是刚能盖住环境,不是“盖住一切”。
所以这篇给你的,不是又一个音频包,是一把能自己造音频的锤子。
05 今晚可以做的一件事
不用下载新 App。电脑上装好 Python,把上面那段存成文件,跑一遍。
十秒后,你手里就多了一段粉红噪音、一段校准音,而且你清楚它们每一格频率是多少。
把那段粉红噪音挪到离床一米外,音量拧到刚能盖住环境,定时关掉。这一步,把第六篇那个调小,变成了你亲手定的小。
亲手定的小,比任何 App 的预设都更靠谱。
跑完如果好奇,用本号配套的音频分析器扫一下 white.wav,斜率应该贴在 0 附近,pink 贴近 -3,brown 贴近 -6,名字对不对,波形说了算。
生成的那段 pink,比 App 大多默认档都轻,也知道它柔在哪。对比一下就明白,为什么第六篇说机器默认档往往太响。
分析器和生成脚本一样,都是纯标准库,能直接接上。下一篇,我打算讲怎么用手机,粗略量出你房间真实的分贝,把第五、第六篇那个分贝从概念变成你手里的读数。
睡眠这事儿,能自己掌握的部分,尽量自己掌握。你更想要哪一种声音当背景?
留言告诉我,我把上面对应的那段单独发你。晚安。















































































