夜雨聆风学习资料网

ARTICLE · 1288680

白噪音最该信的,不是 App,是你自己造的

白噪音最该信的,不是 App,是你自己造的

> 第七篇 · 动手造一段声音

上篇让你拿那段 1kHz 校准音,估一估房间里的分贝。这期顺着它往下讲,它到底是谁造的,怎么造的,以及为什么你其实可以自己造一个。

先说结论,免得你滑到末尾。

你手机 App 里那些“深度白噪音”“高级棕噪音”,背后大概率是同一段录音改了个名字。

但本号之前发的那三条不是,它们是按频谱定义一段一段算出来的。

这篇把那段代码,原样交给你。

你可能会想,自己造一段声音,跟睡得好不好,到底有什么关系?

关系是有的。

第六篇讲过,机器和 App 的默认档,往往比你该听的音量响得多。你如果能自己造一段、并且知道它的电平,就不用去猜那个档位到底几十分贝。

白噪音、粉红噪音、棕噪音,这三个词第三篇就出现过,当时只讲了它们听感差在哪儿。

这期补上它们是怎么来的。

01 三种噪音,差在频谱形状,不在名字多玄

白噪音,是所有频率一样响。数学上,它就是一串彼此独立的随机数。

粉红噪音,是白噪音过了道整形,每翻一倍频率,能量掉 3 分贝,听感比白噪音柔,像稳定下着的雨。

棕噪音掉得更狠,每倍频程掉 6 分贝,低频堆得多,像远处的雷,像空调外机。

区别不是哪种更高级,是频谱的形状不同。三种噪音像三种粗细不同的砂,白的是面粉,粉的是细砂,棕的是粗砂,撒在同一块地上分布不一样,但都是砂。

名字会骗人,波形不会。

本号测过一条标着“白噪音”的现成素材,实测斜率是 -7.31 dB/oct,那是棕噪音的频谱。波形不认广告话术,它只认物理。

所以与其盯着 App 标的名字,不如自己扫一眼斜率。一张表,把三种噪音的长相摆清楚。

噪音
每倍频程能量
听感
像什么
白噪音
约 0 dB/oct
平、略带刺
电视雪花
粉红噪音
约 -3 dB/oct
柔、稳定
稳定下雨
棕噪音
约 -6 dB/oct
闷、厚
远处雷声

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 大多默认档都轻,也知道它柔在哪。对比一下就明白,为什么第六篇说机器默认档往往太响。

分析器和生成脚本一样,都是纯标准库,能直接接上。下一篇,我打算讲怎么用手机,粗略量出你房间真实的分贝,把第五、第六篇那个分贝从概念变成你手里的读数。

睡眠这事儿,能自己掌握的部分,尽量自己掌握。你更想要哪一种声音当背景?

留言告诉我,我把上面对应的那段单独发你。晚安。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料