|
|
当左耳听到 200Hz,右耳听到 210Hz,大脑的上橄榄核(MSO)神经元会自动做 一次减法: 200Hz − 210Hz = 10Hz 这个 10Hz,就是 Alpha 波的中心频率。
这种声音叫 双耳节拍(Binaural Beat)。核心方法给左右耳分别播放两个非常接近、但略有不同频率的纯音。
假设:
左耳:200 Hz
右耳:210 Hz
那么:
左耳 右耳
│ │
▼ ▼
200 Hz 210 Hz
纯音 纯音
│ │
└─────── 大脑听觉系统 ───────┘
│
▼
感知到 10 Hz 的节拍
也就是说,两个声音必须分别进入两个耳朵。
最简单的实现方式就是:
Left channel = 200 Hz sine wave
Right channel = 210 Hz sine wave
然后通过立体声耳机播放。
用 Python 生成
import numpy as np
import soundfile as sf
# ============================================================
# 1. 设置音频的采样率
# ============================================================
# 44100 Hz 表示:
# 一秒钟的声音,会被计算机记录/生成 44100 个采样点。
#
# 44100 Hz 是非常常见的音频采样率,例如 WAV、音乐等经常使用。
#
# 这里生成的是 200 Hz / 210 Hz 的低频纯音,
# 44100 Hz 的采样率完全可以满足需求。
sample_rate = 44100
# ============================================================
# 2. 设置音频持续时间
# ============================================================
# 音频持续 60 秒
duration = 60
# ============================================================
# 3. 设置左右耳播放的声音频率
# ============================================================
# 左耳播放 200 Hz
left_freq = 200
# 右耳播放 210 Hz
right_freq = 210
# ============================================================
# 4. 计算双耳节拍频率
# ============================================================
# 双耳节拍的频率差:
#
# 210 Hz - 200 Hz = 10 Hz
#
# 因此这里的目标节拍频率就是 10 Hz。
#
# 注意:
# 10 Hz 是两个声音之间的频率差,
# 并不是我们直接生成一个 10 Hz 的声音。
#
# 实际生成的声音仍然是:
#
# 左耳:200 Hz
# 右耳:210 Hz
#
# 10 Hz 是两者之间的差频。
beat_freq = abs(right_freq - left_freq)
print(f"左耳频率:{left_freq} Hz")
print(f"右耳频率:{right_freq} Hz")
print(f"双耳节拍频率:{beat_freq} Hz")
# ============================================================
# 5. 创建时间轴
# ============================================================
# 总采样点数量:
#
# 采样率 × 持续时间
#
# 44100 × 60
# = 2,646,000 个采样点
#
# 也就是说,60 秒的音频,
# 左声道有 2,646,000 个数据点,
# 右声道也有 2,646,000 个数据点。
total_samples = int(sample_rate * duration)
# np.arange(total_samples)
#
# 会生成:
#
# 0
# 1
# 2
# 3
# ...
# 2645999
#
# 这些实际上是“第几个采样点”。
#
# 再除以 sample_rate:
#
# 0 / 44100
# 1 / 44100
# 2 / 44100
# ...
#
# 就把它转换成真正的时间(单位:秒)。
#
# 最终 t 类似于:
#
# 0.000000
# 0.000023
# 0.000045
# 0.000068
# ...
#
# 每两个采样点之间的时间间隔约为:
#
# 1 / 44100 ≈ 0.00002268 秒
t = np.arange(total_samples) / sample_rate
# ============================================================
# 6. 生成左耳 200 Hz 的正弦波
# ============================================================
# 正弦波公式:
#
# y(t) = sin(2πft)
#
# 其中:
#
# f = 频率
# t = 时间
#
# 这里:
#
# f = 200 Hz
#
# 所以:
#
# left = sin(2π × 200 × t)
#
# 得到的就是一个 200 Hz 的纯正弦波。
#
# left 是一个 NumPy 数组,
# 长度为 2,646,000。
left = np.sin(
2 * np.pi * left_freq * t
)
# ============================================================
# 7. 生成右耳 210 Hz 的正弦波
# ============================================================
# 同样使用:
#
# y(t) = sin(2πft)
#
# 但右耳的频率是 210 Hz。
#
# 因此:
#
# right = sin(2π × 210 × t)
#
# 得到的是 210 Hz 的纯正弦波。
right = np.sin(
2 * np.pi * right_freq * t
)
# ============================================================
# 8. 把左右两个声音组合成“立体声”
# ============================================================
# 此时:
#
# left:
# [左耳的第1个采样点,
# 左耳的第2个采样点,
# 左耳的第3个采样点,
# ...]
#
# right:
# [右耳的第1个采样点,
# 右耳的第2个采样点,
# 右耳的第3个采样点,
# ...]
#
# 但是 WAV 立体声需要的数据结构是:
#
# [
# [左声道, 右声道],
# [左声道, 右声道],
# [左声道, 右声道],
# ...
# ]
#
# 也就是:
#
# [
# [left[0], right[0]],
# [left[1], right[1]],
# [left[2], right[2]],
# ...
# ]
#
# np.column_stack() 就是把两个一维数组按“列”组合起来。
stereo = np.column_stack((left, right))
# ============================================================
# 9. 查看音频数据的形状
# ============================================================
# stereo.shape 应该类似:
#
# (2646000, 2)
#
# 第一个数字:
# 2646000
#
# 表示一共有 2,646,000 个采样点。
#
# 第二个数字:
# 2
#
# 表示有两个声道:
#
# 第 0 列 → 左声道
# 第 1 列 → 右声道
print("音频数据形状:", stereo.shape)
# ============================================================
# 10. 写入 WAV 文件
# ============================================================
# 使用 soundfile 的 write() 函数生成 WAV 文件。
#
# 第一个参数:
# "binaural_10hz.wav"
#
# 表示生成的文件名。
#
# 第二个参数:
# stereo
#
# 就是刚才生成的左右声道音频数据。
#
# 第三个参数:
# sample_rate
#
# 告诉 WAV 文件:
#
# 这些数据是按照 44100 Hz 的采样率生成的。
sf.write(
"binaural_10hz.wav",
stereo,
sample_rate
)
# ============================================================
# 11. 输出完成信息
# ============================================================
print("音频生成完成!")
print("文件:binaural_10hz.wav")
print(f"持续时间:{duration} 秒")
print(f"左耳:{left_freq} Hz")
print(f"右耳:{right_freq} Hz")
print(f"频率差:{beat_freq} Hz")
如何在背景音乐中添加双耳节拍
背景音乐作为基础声音,200 Hz/210 Hz 的双耳纯音作为左右声道中的额外信号叠加进去。背景音乐也必须保持正常立体声,而 200/210 Hz 双耳信号要分别叠加到左、右声道
┌── 左:背景音乐 + 200 Hz
背景音乐 ───────────┤
└── 右:背景音乐 + 210 Hz
python实现代码如下:
import numpy as np
import soundfile as sf
# ============================================================
# 1. 参数设置
# ============================================================
# 背景音乐文件
input_file = "background.wav"
# 输出文件
output_file = "binaural_background.wav"
# 双耳节拍频率
left_freq = 200 # 左耳 200 Hz
right_freq = 210 # 右耳 210 Hz
# 双耳纯音的音量
#
# 这里不能直接使用 1.0,否则很容易把背景音乐“压住”。
#
# 0.05 = 5%
# 0.02 = 2%
# 0.01 = 1%
#
# 建议实验时先从 0.02 ~ 0.05 开始测试。
binaural_volume = 0.03
# ============================================================
# 2. 读取背景音乐
# ============================================================
music, sample_rate = sf.read(input_file)
print("背景音乐采样率:", sample_rate)
print("背景音乐数据形状:", music.shape)
# ============================================================
# 3. 判断背景音乐是不是立体声
# ============================================================
if music.ndim != 2 or music.shape[1] != 2:
raise ValueError(
"背景音乐必须是立体声 WAV 文件,"
"格式应该是 (采样点数量, 2)"
)
# ============================================================
# 4. 获取背景音乐长度
# ============================================================
total_samples = len(music)
# 音频持续时间
duration = total_samples / sample_rate
print(f"背景音乐长度:{duration:.2f} 秒")
# ============================================================
# 5. 创建时间轴
# ============================================================
t = np.arange(total_samples) / sample_rate
# ============================================================
# 6. 生成 200 Hz / 210 Hz 双耳纯音
# ============================================================
# 左耳 200 Hz
left_binaural = np.sin(
2 * np.pi * left_freq * t
)
# 右耳 210 Hz
right_binaural = np.sin(
2 * np.pi * right_freq * t
)
# ============================================================
# 7. 调整双耳纯音的音量
# ============================================================
left_binaural *= binaural_volume
right_binaural *= binaural_volume
# ============================================================
# 8. 分别叠加到背景音乐左右声道
# ============================================================
# 背景音乐左声道
music_left = music[:, 0]
# 背景音乐右声道
music_right = music[:, 1]
# 左:
# 背景音乐 + 200 Hz
new_left = music_left + left_binaural
# 右:
# 背景音乐 + 210 Hz
new_right = music_right + right_binaural
# ============================================================
# 9. 重新组合成立体声
# ============================================================
result = np.column_stack(
(new_left, new_right)
)
# ============================================================
# 10. 防止音频削波
# ============================================================
# 如果背景音乐本身已经接近最大音量,
# 再叠加双耳纯音后可能超过 [-1, 1]。
#
# 超过这个范围可能造成失真。
#
# 所以这里进行限制。
result = np.clip(result, -1.0, 1.0)
# ============================================================
# 11. 保存最终音频
# ============================================================
sf.write(
output_file,
result,
sample_rate
)
print()
print("生成完成!")
print("输出文件:", output_file)
print(f"左耳:{left_freq} Hz")
print(f"右耳:{right_freq} Hz")
print(f"双耳节拍:{abs(right_freq - left_freq)} Hz")
print(f"双耳音量系数:{binaural_volume}")
如果你的背景音乐是 MP3
上面的 soundfile 更适合 WAV/FLAC 等格式。如果你的背景音乐是:
background.mp3
我建议下一步直接
用 FFmpeg + Python 来处理,因为可以保留 MP3、WAV 等常见格式,并且还能加入淡入淡出、双耳节拍音量渐变、循环背景音乐等功能。
同样演奏一个 200 Hz 的音符,笛子和钢琴听起来完全不同。
声音的“时间结构”
实际上,音色不仅仅由频谱决定,还与声音随时间怎么变化有关。
例如钢琴:
音量
│
│ /\
│ / \
│ / \______
│ /
│___/ \____
└────────────────────────→ 时间
Attack Decay Release
钢琴按下琴键之后:
瞬间出现
↓
快速达到峰值
↓
逐渐衰减
↓
最后消失
这就是典型的 ADSR 包络中的攻击、衰减和释放特征。
笛子则可能更像:
音量
│
│ ┌──────────────
│ /
│ /
│_____/
└────────────────────────→ 时间
演奏者可以持续吹气,因此声音可以保持相对稳定。
5. 从声学角度看,可以把乐器声音拆成三个部分
一个实际乐器声音大致可以理解为:
乐器声音
│
├── 基频
│
├── 谐波 / 泛音
│
└── 噪声 + 时间包络
所以:
纯正弦波
↓
只有基频
↓
听起来非常纯
而:
笛声
↓
基频 + 谐波 + 气息噪声 + 包络
以及:
钢琴
↓
基频 + 大量泛音 + 非谐性 + 击弦瞬态 + 衰减包络
left = np.sin(2 * np.pi * 200 * t)
right = np.sin(2 * np.pi * 210 * t)
这是:
左耳 → 纯 200 Hz
右耳 → 纯 210 Hz
如果把它换成笛声或钢琴声,就完全不是这么简单了。
例如:
左耳笛声:200 Hz
右耳笛声:210 Hz
笛声本身可能包含:
左耳:
200
400
600
800
...
右耳:
210
420
630
840
...
于是你实际上给听觉系统输入了一整组频率,而不仅仅是 200 和 210 Hz。
这可能产生多个频率之间的相互作用。
真实乐器声音是“很多频率按照特定比例和时间结构叠加出来的复杂波形”
|
|