Python音频处理与MIDI制作:从五声调式到TTS语音合成(12实验全实战)
Python音频处理与MIDI制作:从五声调式到TTS语音合成(12实验全实战)
服务器环境:华为云 FlexusX x2e.8u.16g | Ubuntu 24.04 | Python 3.12.3
核心依赖:mido 1.3.3 | numpy 2.5.0 | scipy 1.18.0 | edge-tts 7.2.8
实验服务器:ecs-d2c2-0001 (113.44.212.38) | 8vCPU 16GiB
上机时间:2026-07-03
目录
| 章节 | 模块 | 实验数 | 核心技术 |
|---|---|---|---|
| 第1章 | 节奏基础 | 8 | 五声调式/作曲/模进/宋词 |
| 第2章 | MIDI制作 | 2 | mido/MIDI消息/打击乐 |
| 第3章 | 音频处理 | 2 | edge-tts/WAV/FFT/方言 |
环境架构
┌─────────────────────────────────────────────────────┐
│ ecs-d2c2-0001 (113.44.212.38) │
│ Ubuntu 24.04 | 8vCPU | 16GiB │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ Python │ │ NumPy │ │ SciPy (FFT) │ │
│ │ 3.12.3 │ │ 2.5.0 │ │ 1.18.0 │ │
│ └────┬─────┘ └────┬─────┘ └────────┬──────────┘ │
│ │ │ │ │
│ ┌────▼─────────────▼──────────────────▼──────────┐ │
│ │ 音频处理核心层 │ │
│ ├────────────────┬───────────────────────────────┤ │
│ │ mido 1.3.3 │ edge-tts 7.2.8 │ │
│ │ (MIDI制作) │ (微软TTS引擎) │ │
│ │ → .mid文件 │ → .mp3文件 │ │
│ └────────────────┴───────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ wave + struct │ │
│ │ (WAV波形生成/解析) → .wav文件 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 产出文件: 8 WAV + 4 MIDI + 9 MP3 = 21个音频文件 │
└─────────────────────────────────────────────────────┘
安装依赖
pip3 install --break-system-packages \
--index-url https://mirrors.aliyun.com/pypi/simple/ \
mido numpy scipy edge-tts
验证安装:
$ python3 -c "import mido; print('mido OK')"
$ python3 -c "import edge_tts; print('edge-tts', edge_tts.__version__)"
edge-tts 7.2.8
$ python3 -c "import numpy; print('numpy', numpy.__version__)"
numpy 2.5.0
第1章 节奏基础
实验1:断竹·弹歌 — 竹文化与音乐之始
文化背景
《弹歌》是中国最古老歌谣之一,载于《吴越春秋》:
断竹,续竹;飞土,逐肉。
释义:砍竹 → 接竹为弓 → 射泥丸 → 追猎物。四个动词勾勒出原始狩猎场景。
竹制乐器音高
| 音名 | 频率(Hz) | MIDI编号 | 对应字 |
|---|---|---|---|
| D4 | 293.66 | 62 | 断竹 |
| E4 | 329.63 | 64 | 续竹 |
| G4 | 392.00 | 67 | 飞土 |
| A4 | 440.00 | 69 | 逐肉 |
竹林七贤与音乐
嵇康: 《广陵散》—— 临刑抚琴,千古绝响
阮籍: 《酒狂》—— 醉酒佯狂,古琴名曲
山涛: 好老庄,通音律
向秀: 《思旧赋》—— 悼嵇康
刘伶: 《酒德颂》—— 以酒为乐
阮咸: 善弹琵琶(乐器"阮咸"即以其命名)
王戎: 聪颖善鉴
苏轼《定风波·莫听穿林打叶声》:
莫听穿林打叶声,何妨吟啸且徐行。
竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。
生成弹歌旋律WAV
SAMPLE_RATE = 44100
def save_wav(filename, freqs, durations, volume=0.5, sample_rate=SAMPLE_RATE):
"""将频率列表和时长列表合成为WAV文件"""
samples = np.array([], dtype=np.float32)
for freq, dur in zip(freqs, durations):
if freq <= 0:
n = int(dur * sample_rate)
seg = np.zeros(n, dtype=np.float32)
else:
n = int(dur * sample_rate)
t = np.linspace(0, dur, n, endpoint=False)
# 基波 + 2次/3次谐波,模拟竹笛音色
seg = (volume * np.sin(2 * np.pi * freq * t)
+ 0.3 * volume * np.sin(2 * np.pi * freq * 2 * t)
+ 0.1 * volume * np.sin(2 * np.pi * freq * 3 * t))
# ADSR包络
attack = min(int(0.01 * sample_rate), n // 4)
release = min(int(0.05 * sample_rate), n // 4)
if attack > 0:
seg[:attack] *= np.linspace(0, 1, attack)
if release > 0:
seg[-release:] *= np.linspace(1, 0, release)
samples = np.concatenate([samples, seg.astype(np.float32)])
# 归一化
max_val = np.max(np.abs(samples))
if max_val > 0:
samples = samples / max_val * 0.8
pcm = (samples * 32767).astype(np.int16)
with wave.open(filename, "w") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(pcm.tobytes())
服务器输出:
生成WAV: /tmp/exp01_tange.wav (326384 bytes)
王羲之《兰亭集序》:茂林修竹,清流激湍,映带左右。
郑板桥《竹石》:咬定青山不放松,立根原在破岩中。
实验2:中国传统五声调式 — 宫商角徵羽
五声调式音阶对照表
| 音名 | 唱名 | 频率(Hz) | MIDI | 与前音音程 | 特性 |
|---|---|---|---|---|---|
| 宫© | do | 261.63 | 60 | 起点 | 中央C,五声之首 |
| 商(D) | re | 293.66 | 62 | 大二度 | 商音清亮 |
| 角(E) | mi | 329.63 | 64 | 大二度 | 角音柔和 |
| 徵(G) | sol | 392.00 | 67 | 小三度 | 徵音高亢 |
| 羽(A) | la | 440.00 | 69 | 大二度 | 羽音悠远 |
音程关系
宫 → 商: 2半音 (大二度/全音)
商 → 角: 2半音 (大二度/全音)
角 → 徵: 3半音 (小三度) ← 跳过F(清角)
徵 → 羽: 2半音 (大二度)
羽 → 宫: 3半音 (小三度) ← 跳过B(变宫)
关键:五声调式无半音关系(没有fa和si),因此听起来和谐、东方韵味浓厚。西方七声自然大调含半音关系(E-F, B-C),产生不协和感。
五种调式
| 调式 | 音阶 | 唱名 | 色彩 |
|---|---|---|---|
| 宫调式 | C-D-E-G-A-C | do-re-mi-sol-la-do | 大调色彩,明亮 |
| 商调式 | D-E-G-A-C-D | re-mi-sol-la-do-re | 微暗淡 |
| 角调式 | E-G-A-C-D-E | mi-sol-la-do-re-mi | 清丽 |
| 徵调式 | G-A-C-D-E-G | sol-la-do-re-mi-sol | 明亮 |
| 羽调式 | A-C-D-E-G-A | la-do-re-mi-sol-la | 小调色彩 |
生成WAV:
生成WAV: /tmp/exp02_pentatonic.wav (158804 bytes)
实验3:五声调式歌曲创作 — 民族风旋律动机与节奏型
旋律创作三要素
1. 音高序列 — 使用五声调式音阶(C D E G A)
2. 节奏型 — 音的长短组合(全音符/二分/四分/八分/十六分)
3. 力度 — 音的强弱变化
音符时值对照表
| 名称 | 时值(拍) | 符号 |
|---|---|---|
| 全音符 | 4.00 | 𝅝 |
| 二分音符 | 2.00 | 𝅗𝅥 |
| 四分音符 | 1.00 | ♩ |
| 八分音符 | 0.50 | ♪ |
| 十六分音符 | 0.25 | 𝅘𝅥𝅯 |
| 附点四分 | 1.50 | ♩. |
| 附点八分 | 0.75 | ♪. |
古风旋律《竹林晚风》
调式: C宫五声调式 | 拍号: 4/4 | 速度: ♩=80
起承转合结构(宫商角徵羽记谱):
| 阶段 | 宫商角徵羽 | 音名 |
|---|---|---|
| 起(4小节) | 角 徵 羽 宫 羽 徵 角 商 | E4 G4 A4 C5 A4 G4 E4 D4 |
| 承(4小节) | 宫 商 角 徵 角 商 宫 〇 | C4 D4 E4 G4 E4 D4 C4 rest |
| 转(4小节) | 徵 羽 宫 羽 徵 角 商 〇 | G4 A4 C5 A4 G4 E4 D4 rest |
| 合(4小节) | 宫 角 商 宫 商 宫 〇 〇 | C4 E4 D4 C4 D4 C4 rest rest |
生成WAV: /tmp/exp03_gufeng.wav (1190716 bytes)
常见节奏型
| 节奏型 | 拍值 | 力度模式 |
|---|---|---|
| 进行曲 | 1+1+1+1=4拍 | 强 弱 强 弱 |
| 华尔兹 | 0.75+0.25+1=2拍 | 强 弱弱 |
| 摇滚 | 0.5+0.5+1+0.5+0.5=3拍 | 强 弱 次强 弱 弱 |
| 切分音 | 0.75+0.25+0.5+0.5=2拍 | 强→弱→弱→强 |
| 散板 | 0.5+1+0.25+0.25+1=3拍 | 自由节奏 |
实验4:短歌行 — 韵脚·力度·二维数组·五声调式作曲
曹操《短歌行》:
对酒当歌,人生几何!譬如朝露,去日苦多。
慨当以慷,忧思难忘。何以解忧?唯有杜康。
韵脚分析
| 诗句 | 韵脚 | 韵类 |
|---|---|---|
| 对酒当歌,人生几何 | 歌/何 | e韵 |
| 譬如朝露,去日苦多 | 露/多 | o韵 |
| 慨当以慷,忧思难忘 | 慷/忘 | ang韵 |
| 何以解忧,唯有杜康 | 忧/康 | ou/ang韵 |
二维数组编码
每个字编码为 [字, 音名, 时值(拍), 力度(0-1)]:
composition = [
["对", "D4", 0.5, 0.7], ["酒", "E4", 0.5, 0.6],
["当", "G4", 1.0, 0.8], ["歌", "A4", 1.0, 0.9],
["人", "G4", 0.5, 0.6], ["生", "E4", 0.5, 0.5],
["几", "D4", 0.5, 0.7], ["何", "C4", 1.5, 0.9],
# ... 后续各句
]
服务器输出(节选):
字 音名 频率(Hz) 时值(拍) 力度 MIDI
-------------------------------------------------------
对 D4 293.66 0.5 ███████░░░ 62
酒 E4 329.63 0.5 ██████░░░░ 64
当 G4 392.00 1.0 ████████░░ 67
歌 A4 440.00 1.0 █████████░ 69
人 G4 392.00 0.5 ██████░░░░ 67
生 E4 329.63 0.5 █████░░░░░ 64
几 D4 293.66 0.5 ███████░░░ 62
何 C4 261.63 1.5 █████████░ 60
...
康 C4 261.63 2.0 ██████████ 60
生成WAV: /tmp/exp04_duange.wav (1296578 bytes)
实验5:四言绝句·古诗AI作曲 — 起承转合·关雎
《诗经·周南·关雎》:
关关雎鸠,在河之洲。窈窕淑女,君子好逑。
起承转合作曲法
| 阶段 | 作用 | 音区 | 节奏 |
|---|---|---|---|
| 起 | 呈示主题(关关雎鸠) | 中低,平稳 | 中速 |
| 承 | 重复/延伸(在河之洲) | 升高 | 加快上行 |
| 转 | 对比/转折(窈窕淑女) | 引入新音调 | 切分变化 |
| 合 | 回归主题(君子好逑) | 收束 | 圆满 |
AI声调映射算法
将汉字四声映射到五声调式:
| 声调 | 调名 | 映射音名 | 频率(Hz) |
|---|---|---|---|
| 阴平(1声) | 平 | C4 (宫) | 261.63 |
| 阳平(2声) | 升 | E4 (角) | 329.63 |
| 上声(3声) | 降升 | G4 (徵) | 392.00 |
| 去声(4声) | 降 | A4 (羽) | 440.00 |
服务器输出:
字 声调 调名 映射音名 频率(Hz) 阶段
------------------------------------------------------------
关 1 阴平 C4 261.63 起
关 1 阴平 C4 261.63 起
雎 1 阴平 C4 261.63 起
鸠 1 阴平 C4 261.63 起
在 4 去声 A4 440.00 承
河 2 阳平 E4 329.63 承
之 1 阴平 C4 261.63 承
洲 1 阴平 C4 261.63 承
窈 3 上声 G4 392.00 转
窕 3 上声 G4 392.00 转
淑 1 阴平 C4 261.63 转
女 3 上声 G4 392.00 转
君 1 阴平 C4 261.63 合
子 3 上声 G4 392.00 合
好 4 去声 A4 440.00 合
逑 2 阳平 E4 329.63 合
生成WAV: /tmp/exp05_guanju.wav (529244 bytes)
起承转合节奏特征
| 阶段 | 节奏型 | 总拍 | 特征 |
|---|---|---|---|
| 起 | 0.5+0.5+1.0+1.0 | 3.0 | 平稳,中速 |
| 承 | 0.5+0.5+0.5+0.5+1.0 | 3.0 | 加快,上行 |
| 转 | 0.25+0.25+0.5+1.0 | 2.0 | 切分,变化 |
| 合 | 0.5+0.5+0.5+0.5+1.0 | 3.0 | 收束,圆满 |
实验6:节奏型·变调 — rhythm_pattern·时长·强弱
节奏型大全
| 名称 | 拍值 | 图示 | 应用 |
|---|---|---|---|
| 均匀型 | 1+1+1+1 | ●●●● | 进行曲、儿歌 |
| 长短型 | 1.5+0.5+1+1 | ●.●●● | 附点节奏 |
| 短长型 | 0.5+1.5+1+1 | ●●.●● | 切分效果 |
| 三连音 | 0.67+0.67+0.66+1 | ◐◐◐● | 爵士摇摆 |
| 前八后十六 | 0.5+0.25+0.25+1 | ♪𝅘𝅥𝅯● | 进行曲 |
| 前十六后八 | 0.25+0.25+0.5+1 | 𝅘𝅥𝅯♪● | 舞曲 |
| 休止前奏 | 0+1+0.5+1.5 | ○●◐●. | 弱起拍 |
变调(Transposition)
变调 = 将旋律中所有音升高或降低相同的半音数。
原始旋律(C宫): C4 D4 E4 G4 A4
| 变调 | 半音数 | 结果 | 频率比率 |
|---|---|---|---|
| 升高大二度(D宫) | +2 | D4 E4 F#4 A4 B4 | 1.1225 |
| 升高纯四度(F宫) | +5 | F4 G4 A4 C5 D5 | 1.3348 |
| 升高纯五度(G宫) | +7 | G4 A4 B4 D5 E5 | 1.4983 |
| 降低大二度(B宫) | -2 | A#3 C4 D4 F4 G4 | 0.8909 |
数学原理:频率比率 = 2^(半音数/12)。每升高一个八度(12半音),频率翻倍。
拍号强弱规律
| 拍号 | 拍数 | 强弱模式 | 应用 |
|---|---|---|---|
| 2/4 | 2拍/小节 | 强 弱 | 进行曲 |
| 3/4 | 3拍/小节 | 强 弱 弱 | 华尔兹 |
| 4/4 | 4拍/小节 | 强 弱 次强 弱 | 流行音乐 |
| 6/8 | 6拍/小节 | 强 弱弱 次强弱弱 | 船歌 |
生成WAV: /tmp/exp06_transpose.wav (432224 bytes)
实验7:酒干倘卖无 — 模进上行·模进下行
模进(Sequence)原理
模进 = 将一个旋律动机在不同音高上重复出现
模进上行: 每次重复时升高音高(情绪递进、推向高潮)
模进下行: 每次重复时降低音高(情绪回落、收束)
《酒干倘卖无》— 苏芮演唱,侯德健作曲。(酒干=空酒瓶,倘卖无=有没有卖)
模进上行示例
动机: C-E-G (三音上行),每次升高大二度(+2半音)
| 第几次 | 半音偏移 | 音名 | 频率(Hz) |
|---|---|---|---|
| 原型 | 0 | C4 E4 G4 | 261.6 / 329.6 / 392.0 |
| +2 | 2 | D4 F#4 A4 | 293.7 / 370.0 / 440.0 |
| +4 | 4 | E4 G#4 B4 | 329.6 / 415.3 / 493.9 |
| +6 | 6 | F#4 A#4 C#5 | 370.0 / 466.2 / 554.4 |
| +8 | 8 | G#4 C5 D#5 | 415.3 / 523.3 / 622.3 |
模进下行示例
动机: A-G-E (三音下行),每次降低大二度(-2半音)
| 第几次 | 半音偏移 | 音名 | 频率(Hz) |
|---|---|---|---|
| 原型 | 0 | A4 G4 E4 | 440.0 / 392.0 / 329.6 |
| -2 | -2 | G4 F4 D4 | 392.0 / 349.2 / 293.7 |
| -4 | -4 | F4 D#4 C4 | 349.2 / 311.1 / 261.6 |
| -6 | -6 | D#4 C#4 A#3 | 311.1 / 277.2 / 233.1 |
| -8 | -8 | C#4 B3 G#3 | 277.2 / 246.9 / 207.7 |
模进的情感效果
| 类型 | 情感效果 | 应用场景 |
|---|---|---|
| 模进上行 | 情绪逐渐高涨,推向高潮 | 副歌前奏、情绪积累 |
| 模进下行 | 情绪逐渐舒缓,归于平静 | 尾声、收束段落 |
| 自由模进 | 保持音程关系但允许微调 | 爵士即兴 |
| 严格模进 | 完全保持音程关系 | 古典赋格 |
生成WAV: /tmp/exp07_sequence.wav (882044 bytes)
实验8:宋词 — 词牌旋律创作
宋词与音乐
宋词本为配乐演唱的歌词,每个词牌对应固定曲调。词牌名 = 曲调名。
苏轼《水调歌头·明月几时有》:
明月几时有?把酒问青天。不知天上宫阙,今夕是何年。
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。
水调歌头词牌格律
上阕: 5-5-6-5-6-5-6 (字数)
下阕: 2-4-4-5-6-5-6 (字数)
押韵: 平韵一韵到底 (天/年/寒/间)
为《水调歌头》作曲
调式: D羽调式 (A-C-D-E-G-A) | 拍号: 散板→4/4
上阕38字逐字配音(节选):
字 音名 频率(Hz) 时值(拍) 力度 阶段
--------------------------------------------------
明 A4 440.00 1.0 ███████░░░ 起
月 G4 392.00 0.5 ██████░░░░ 起
几 E4 329.63 0.5 █████░░░░░ 起
时 G4 392.00 0.5 ██████░░░░ 起
有 A4 440.00 1.5 ████████░░ 起
把 D4 293.66 0.5 ██████░░░░ 承
酒 E4 329.63 0.5 █████░░░░░ 承
问 G4 392.00 1.0 ███████░░░ 承
青 A4 440.00 0.5 ███████░░░ 承
天 C5 523.25 2.0 █████████░ 承
...
寒 D4 293.66 2.0 █████████░ 合
生成WAV: /tmp/exp08_shuidiao.wav (1256894 bytes)
常见词牌音乐特点
| 词牌名 | 字数 | 风格 | 代表作 |
|---|---|---|---|
| 水调歌头 | 95字 | 雄浑豪放 | 苏轼《明月几时有》 |
| 念奴娇 | 100字 | 激越豪壮 | 苏轼《赤壁怀古》 |
| 蝶恋花 | 60字 | 缠绵悱恻 | 柳永《伫倚危楼风细细》 |
| 满江红 | 93字 | 慷慨激昂 | 岳飞《怒发冲冠》 |
| 雨霖铃 | 103字 | 凄婉哀怨 | 柳永《寒蝉凄切》 |
| 声声慢 | 97字 | 凄凉悲苦 | 李清照《寻寻觅觅》 |
第2章 MIDI制作
实验9:搭建基础环境 — MIDI制作
MIDI基础概念
MIDI (Musical Instrument Digital Interface)
- 数字音乐的国际标准协议
- 传输的是音乐事件(音符开/关/力度/控制器),不是音频
- 文件极小: 1分钟MIDI约2-10KB,WAV约5-10MB
MIDI消息类型
| 消息类型 | 说明 | 参数 | 状态字节 |
|---|---|---|---|
| note_on | 音符按下 | channel, note, velocity | 0x90-0x9F |
| note_off | 音符释放 | channel, note, velocity | 0x80-0x8F |
| control_change | 控制器变化 | channel, control, value | 0xB0-0xBF |
| program_change | 音色切换 | channel, program | 0xC0-0xCF |
| pitchwheel | 弯音轮 | channel, pitch | 0xE0-0xEF |
| meta | 元信息 | type, value | 0xFF |
General MIDI 标准音色表
| 编号 | 英文名 | 中文名 |
|---|---|---|
| 0 | Acoustic Grand Piano | 大钢琴 |
| 1 | Bright Acoustic Piano | 明亮钢琴 |
| 4 | Electric Piano 1 | 电钢琴 |
| 24 | Acoustic Guitar (nylon) | 尼龙弦吉他 |
| 30 | Distortion Guitar | 失真吉他 |
| 40 | Violin | 小提琴 |
| 42 | Cello | 大提琴 |
| 56 | Trumpet | 小号 |
| 73 | Flute | 长笛 |
| 78 | Shakuhachi | 尺八(日本竹笛) |
GM标准打击乐器 (Channel 10)
| 音符 | 英文 | 中文 |
|---|---|---|
| 35 | Acoustic Bass Drum | 底鼓 |
| 36 | Bass Drum 1 | 大鼓 |
| 38 | Acoustic Snare | 军鼓/小鼓 |
| 42 | Closed Hi-Hat | 闭合踩镲 |
| 46 | Open Hi-Hat | 开放踩镲 |
| 49 | Crash Cymbal 1 | 吊镲 |
| 51 | Ride Cymbal 1 | 叮叮镲 |
| 52 | Chinese Cymbal | 中国镲 |
用mido创建MIDI文件
from mido import Message, MidiTrack, MidiFile, MetaMessage, bpm2tempo
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
# 设置速度 BPM=120
track.append(MetaMessage("set_tempo", tempo=bpm2tempo(120), time=0))
# 设置音色: 0 = 大钢琴
track.append(Message("program_change", program=0, channel=0, time=0))
# C大调音阶
scale_notes = [60, 62, 64, 65, 67, 69, 71, 72] # C4 D4 E4 F4 G4 A4 B4 C5
for note in scale_notes:
velocity = 80 if note % 2 == 0 else 60
track.append(Message("note_on", note=note, velocity=velocity, time=0))
track.append(Message("note_off", note=note, velocity=0, time=480)) # 四分音符
mid.save("/tmp/exp09_scale.mid")
服务器输出:
保存MIDI: /tmp/exp09_scale.mid (123 bytes)
读取并分析MIDI文件:
MIDI类型: 1
ticks_per_beat: 480
音轨数: 1
音轨 0: Demo Melody
消息数: 20
[ 0] MetaMessage('set_tempo', tempo=500000, time=0)
[ 1] program_change channel=0 program=0 time=0
[ 3] note_on channel=0 note=60 velocity=80 time=0
[ 4] note_off channel=0 note=60 velocity=0 time=480
...
五声调式MIDI旋律
# BPM=90, 音色=长笛(73)
track3.append(Message("program_change", program=73, channel=0, time=0))
pentatonic_melody = [
(60, 480, 80), # C4 四分音符 强
(62, 480, 60), # D4 四分音符 弱
(64, 480, 80), # E4
(67, 960, 70), # G4 二分音符
(69, 480, 80), # A4
(72, 960, 90), # C5 二分音符 高潮
(69, 480, 70), # A4
(67, 480, 60), # G4
(64, 480, 70), # E4
(62, 480, 60), # D4
(60, 960, 90), # C4 二分音符 收束
]
MIDI文件结构分析:
总tick数: 6720
ticks_per_beat: 480
BPM: 90
总时长: 9.33秒
音符数: 11
文件大小: 156 bytes
对比:9.33秒的MIDI仅156 bytes,同等时长的WAV约823KB——MIDI比WAV小5280倍!
实验10:打击乐器的变化
基本鼓点模式 (4/4拍)
GM标准: Channel 10 (0-indexed: channel=9) 专用于打击乐。
时间(tick) 乐器 音符 力度 拍位
-------------------------------------------------------
0 Bass Drum 36 ██████████ 1
0 Hi-Hat(closed) 42 ██████░░░░ 1
240 Hi-Hat(closed) 42 █████░░░░░ 1&
480 Snare 38 █████████░ 2
480 Hi-Hat(closed) 42 ██████░░░░ 2
720 Hi-Hat(closed) 42 █████░░░░░ 2&
960 Bass Drum 36 ██████████ 3
960 Hi-Hat(closed) 42 ██████░░░░ 3
1200 Hi-Hat(closed) 42 █████░░░░░ 3&
1440 Snare 38 █████████░ 4
1440 Hi-Hat(closed) 42 ██████░░░░ 4
1680 Hi-Hat(closed) 42 █████░░░░░ 4&
保存: /tmp/exp10_drum_basic.mid (133 bytes)
MIDI事件列表构建(关键技巧)
# 构建事件列表: (tick, is_note_on, note, velocity)
events = []
for note, tick, vel in drum_pattern:
events.append((tick, 1, note, vel)) # note_on
events.append((tick + 120, 0, note, 0)) # note_off
events.sort(key=lambda x: (x[0], x[1])) # 按tick排序
# 写入MIDI轨道
prev_tick = 0
for tick, is_on, note, vel in events:
delta = max(0, tick - prev_tick)
if is_on:
track.append(Message("note_on", note=note, velocity=vel,
channel=9, time=delta))
else:
track.append(Message("note_off", note=note, velocity=0,
channel=9, time=delta))
prev_tick = tick
踩坑:同时刻多个音符必须用事件列表排序后逐个写入,直接交替note_on/note_off会导致delta time为负值。
三种鼓点变化
变化1: 摇滚节拍 (Rock Beat)
踩镲在每个八分音符位置
底鼓在拍1,3,5,7 (强拍)
军鼓在拍2,4,6,8 (弱拍反拍)
变化2: 华尔兹节拍 (3/4, 3拍/小节)
小节1 拍1: Bass Drum + Hi-Hat (强)
小节1 拍2: Snare + Hi-Hat (弱)
小节1 拍3: Snare + Hi-Hat (弱)
变化3: 切分节奏 (Syncopation)
底鼓: 拍1, 拍2.5(切分), 拍4
军鼓: 拍2, 拍4.5(切分)
踩镲: 每个八分 + 反拍加重
Ghost Notes(幽灵音)
Ghost Note = 极低力度的装饰音,增加律动感。
| 位置 | 乐器 | 力度 | 效果 |
|---|---|---|---|
| 强拍底鼓 | Bass Drum | 120 | ████████████ |
| 弱拍军鼓 | Snare | 40 | ████ |
| 幽灵音 | Snare | 20 | ██ |
| 强拍军鼓 | Snare | 110 | ███████████ |
| 踩镲强 | Hi-Hat | 80 | ████████ |
| 踩镲弱 | Hi-Hat | 35 | ███ |
| 幽灵音 | Snare | 18 | █ |
保存: /tmp/exp10_drum_variations.mid (491 bytes)
第3章 音频处理
实验11:TTS — edge-tts 文字转语音
edge-tts 概述
edge-tts: 微软Edge浏览器TTS引擎的Python接口
- 免费、无需API Key
- 支持300+语音(含中文普通话、粤语、方言)
- 输出MP3格式
- 支持SSML标记(语速、音调调整)
可用中文语音
| 语音名称 | 地区 | 性别 | 特点 |
|---|---|---|---|
| zh-CN-XiaoxiaoNeural | zh-CN | 女 | 温柔亲切 |
| zh-CN-YunxiNeural | zh-CN | 男 | 沉稳阳刚 |
| zh-CN-YunyangNeural | zh-CN | 男 | 新闻播报 |
| zh-CN-XiaochenNeural | zh-CN | 女 | 活泼明快 |
| zh-CN-XiaohanNeural | zh-CN | 女 | 温暖舒缓 |
| zh-CN-XiaomengNeural | zh-CN | 女 | 柔美梦幻 |
| zh-CN-XiaoqiuNeural | zh-CN | 女 | 成熟知性 |
| zh-CN-XiaoruiNeural | zh-CN | 女 | 干练利落 |
| zh-CN-XiaoshuangNeural | zh-CN | 女 | 少女音 |
| zh-CN-XiaoyanNeural | zh-CN | 女 | 标准播音 |
| zh-CN-liaoning-XiaobeiNeural | zh-CN-liaoning | 女 | 东北话 |
| zh-CN-shaanxi-XiaoniNeural | zh-CN-shaanxi | 女 | 陕西话 |
| zh-HK-HiuMaanNeural | zh-HK | 女 | 粤语 |
| zh-TW-HsiaoChenNeural | zh-TW | 女 | 台湾国语 |
TTS合成代码
import edge_tts, asyncio
async def tts_synthesize(text, voice, output_file, rate="+0%", pitch="+0Hz"):
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
await communicate.save(output_file)
# 普通话朗读《短歌行》
text = "对酒当歌,人生几何。譬如朝露,去日苦多。"
asyncio.run(tts_synthesize(text, "zh-CN-XiaoxiaoNeural", "/tmp/tts_poem.mp3"))
服务器输出
【TTS合成示例1: 普通话朗读《短歌行》】
语音: zh-CN-XiaoxiaoNeural (女声)
保存: /tmp/exp11_tts_poem.mp3 (61200 bytes)
【TTS合成示例2: 英语朗读】
语音: en-US-AriaNeural (女声)
保存: /tmp/exp11_tts_english.mp3 (37584 bytes)
【粤语TTS合成】
语音: zh-HK-HiuMaanNeural (粤语女声)
保存: /tmp/exp11_tts_cantonese.mp3 (39600 bytes)
语速与音调控制
测试文本: “宫商角徵羽,五声调式之美。” 语音: zh-CN-YunxiNeural (男声)
| 参数 | rate | pitch | 文件大小 | 效果 |
|---|---|---|---|---|
| 正常语速 | +0% | +0Hz | 22464 bytes | 基准 |
| 慢速朗读 | -30% | +0Hz | 31824 bytes | 语速放慢,文件变大 |
| 快速朗读 | +50% | +0Hz | 15120 bytes | 语速加快,文件变小 |
| 低沉声音 | +0% | -50Hz | 22320 bytes | 音调降低 |
| 高亢声音 | +0% | +50Hz | 22464 bytes | 音调升高 |
实验12:WAV波形文件分析 — 方言
WAV文件格式
WAV (Waveform Audio File Format)
- RIFF (Resource Interchange File Format) 容器
- 无损PCM编码
- 结构: RIFF头(12B) + fmt块(24B) + data块(8B头+音频数据)
WAV二进制结构分析
Offset 0-3: b'RIFF' (RIFF标识)
Offset 4-7: 88236 (文件大小-8)
Offset 8-11: b'WAVE' (WAVE标识)
Offset 12-15: b'fmt ' (fmt 标识)
Offset 16-19: 16 (fmt块大小)
Offset 20-21: 1 (音频格式=1 PCM)
Offset 22-23: 1 (声道数)
Offset 24-27: 44100 (采样率)
Offset 28-31: 88200 (字节率 = 采样率×声道×位深/8)
Offset 32-33: 2 (块对齐 = 声道×位深/8)
Offset 34-35: 16 (位深度 = 16bit)
Offset 36-39: b'data' (data标识)
Offset 40-43: 88200 (音频数据大小)
Python wave模块分析
import wave, numpy as np
with wave.open("/tmp/exp12_test.wav", "rb") as wf:
print(f"声道数: {wf.getnchannels()}") # 1
print(f"采样位深: {wf.getsampwidth()*8} bit") # 16
print(f"采样率: {wf.getframerate()} Hz") # 44100
print(f"总帧数: {wf.getnframes()}") # 44100
print(f"总时长: {wf.getnframes()/wf.getframerate():.3f}秒") # 1.000
frames = wf.readframes(wf.getnframes())
samples = np.frombuffer(frames, dtype=np.int16)
print(f"RMS: {np.sqrt(np.mean(samples.astype(np.float64)**2)):.2f}")
print(f"峰值因子: {np.max(np.abs(samples)) / np.sqrt(np.mean(samples.astype(np.float64)**2)):.2f}")
输出:
声道数: 1
采样位深度: 16 bit
采样率: 44100 Hz
总帧数: 44100
总时长: 1.000 秒
最大振幅: 18486
RMS: 11904.37
峰值因子: 1.55
FFT频谱分析
from scipy.fft import fft, fftfreq
N = len(samples)
yf = fft(samples.astype(np.float64))
xf = fftfreq(N, 1 / SAMPLE_RATE)
magnitude = np.abs(yf[:N//2])
freqs = xf[:N//2]
peak_indices = np.argsort(magnitude)[-5:][::-1]
440Hz(A4)信号的FFT检测结果:
| 频率(Hz) | 幅度 | 说明 |
|---|---|---|
| 440.00 | 350,402,948 | 基频 (A4) |
| 880.00 | 105,116,882 | 2次谐波 |
| 1320.00 | 35,033,800 | 3次谐波 |
| 441.00 | 10,802,444 | 频率分辨率噪声 |
| 439.00 | 10,802,354 | 频率分辨率噪声 |
基频440Hz + 2次谐波880Hz + 3次谐波1320Hz,正是代码中三个正弦波叠加的结果。
不同采样率对比
| 采样率 | 文件大小 | 音质 | 应用场景 |
|---|---|---|---|
| 8000 | 16KB | 电话音质 | 电话/对讲机 |
| 16000 | 32KB | 语音音质 | VoIP语音 |
| 22050 | 44KB | FM音质 | 网络音频 |
| 44100 | 87KB | CD音质 | 音乐CD |
| 48000 | 94KB | DVD音质 | 专业音频/DVD |
方言TTS合成
| 方言 | 语音引擎 | 文件大小 | 文本 |
|---|---|---|---|
| 普通话 | zh-CN-XiaoxiaoNeural | 26KB | 今天天气真好,我们一起去公园散步吧。 |
| 粤语 | zh-HK-HiuMaanNeural | 29KB | 今日天气好好,我哋一齐去公园散步啦。 |
| 台湾国语 | zh-TW-HsiaoChenNeural | 29KB | 今天天气真好,我们一起去公园散步吧。 |
| 四川话模拟 | zh-CN-XiaoyiNeural | 29KB | 今天天气好得很嘛,我们一起去公园走一下嘛。 |
WAV波形ASCII可视化
440Hz正弦波(A4)前200个采样点的ASCII波形图:
------------------------------------------------------------
*
* ***
****** ******* *****
************* ***** **********
**********************
------------------------------------------------------------
采样率: 44100Hz | 位深: 16bit | 采样点: 200
音频格式对比
| 格式 | 编码方式 | 码率 | 音质 | 文件大小 |
|---|---|---|---|---|
| WAV | 无压缩PCM | 1411kbps | CD音质 | 10MB/分钟 |
| MP3 | 有损压缩 | 128-320kbps | 接近CD | 1-3MB/分钟 |
| FLAC | 无损压缩 | 700-1000kbps | 无损 | 5-7MB/分钟 |
| AAC | 有损压缩 | 128-256kbps | 优于MP3 | 1-2MB/分钟 |
| OGG | 有损压缩 | 96-500kbps | 接近MP3 | 1-2MB/分钟 |
| MIDI | 事件序列 | 事件数据 | 依赖音源 | 2-10KB/分钟 |
服务器产出文件清单
| 实验 | 文件 | 格式 | 大小 |
|---|---|---|---|
| 1 | exp01_tange.wav | WAV | 319KB |
| 2 | exp02_pentatonic.wav | WAV | 156KB |
| 3 | exp03_gufeng.wav | WAV | 1.2MB |
| 4 | exp04_duange.wav | WAV | 1.3MB |
| 5 | exp05_guanju.wav | WAV | 517KB |
| 6 | exp06_transpose.wav | WAV | 423KB |
| 7 | exp07_sequence.wav | WAV | 862KB |
| 8 | exp08_shuidiao.wav | WAV | 1.2MB |
| 9 | exp09_scale.mid | MIDI | 123B |
| 9 | exp09_pentatonic.mid | MIDI | 156B |
| 10 | exp10_drum_basic.mid | MIDI | 133B |
| 10 | exp10_drum_variations.mid | MIDI | 491B |
| 11 | exp11_tts_poem.mp3 | MP3 | 60KB |
| 11 | exp11_tts_english.mp3 | MP3 | 37KB |
| 11 | exp11_tts_cantonese.mp3 | MP3 | 39KB |
| 11 | exp11_tts_*.mp3 (5个) | MP3 | 115KB |
| 12 | exp12_test.wav | WAV | 87KB |
| 12 | exp12_wav_*.wav (5个) | WAV | 273KB |
| 12 | exp12_tts_*.mp3 (4个) | MP3 | 113KB |
| 合计 | 29个文件 | ~6.9MB |
踩坑记录
| # | 问题 | 原因 | 解决 |
|---|---|---|---|
| 1 | FluidSynth安装失败 | 华为云镜像源404 | 使用mido纯Python方案,不依赖FluidSynth |
| 2 | MIDI保存报time must be non-negative |
同时刻多个音符交替note_on/off导致delta为负 | 构建事件列表排序后逐个写入 |
| 3 | WAV ASCII可视化OverflowError | int16溢出(threshold>32767) | 转为int32后比较 |
| 4 | PyPI超时 | 华为云香港节点网络限制 | 使用阿里云镜像 mirrors.aliyun.com |
技术要点总结
音名 ↔ 频率 ↔ MIDI编号 换算
# 音名 → 频率 (A4=440Hz为基准)
freq = 440.0 * (2.0 ** ((midi_note - 69) / 12.0))
# 频率 → MIDI编号
midi_note = int(round(12 * math.log2(freq / 440.0) + 69))
# 音名 → MIDI编号
# C4=60, D4=62, E4=64, F4=65, G4=67, A4=69, B4=71, C5=72
五声调式核心
宫(do) 商(re) 角(mi) 徵(sol) 羽(la)
C D E G A
无半音关系 → 和谐东方韵味
MIDI vs WAV 文件大小
| 属性 | MIDI | WAV (CD音质) |
|---|---|---|
| 编码方式 | 事件序列 | PCM采样 |
| 1分钟大小 | 2-10KB | ~10MB |
| 压缩比 | ~1000:1 | 1:1 |
| 音质 | 依赖音源 | 固定不变 |
| 编辑性 | 易编辑 | 难编辑 |
edge-tts 语速/音调参数
# rate: 语速调整 (-100% 到 +100%)
# pitch: 音调调整 (-50Hz 到 +50Hz)
edge_tts.Communicate(text, voice, rate="-30%", pitch="+50Hz")
更多推荐



所有评论(0)