Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 3 column 1
---
- oeasy Python 0118
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
本教程同步发布在:
个人网站: `https://oeasy.org`
蓝桥云课: `https://www.lanqiao.cn/courses/3584`
GitHub: `https://github.com/overmind1980/oeasy-python-tutorial`
Gitee: `https://gitee.com/overmind1980/oeasypython`
---- 配套视频
- 上次了解了 音符四要素
| 要素 | 定义 | 影响因素 | 作用 | 举例 |
|---|---|---|---|---|
| 音高 | 音符的高低 | 发声体振动频率 | 构建旋律与和声框架 | 钢琴从左到右音高渐高 |
| 时长 | 音符发声持续的时间 | 发声体振动持续时长 | 时间上的长短比例 形成节奏 | 全音符、四分音符 |
| 音强 | 音符发声的强弱程度 | 发声体振动幅度 | 情感起伏 动态变化 | 激昂用强音 轻柔 用弱音 |
| 音色 | 音符声音的特色 | 发声体的材质、结构、发声方式等 | 赋予声音独特个性 | 钢琴清脆明亮 二胡悠扬 |
- 设置好 音轨的音色之后
- 每个音符 都有自己的四要素
- 音色
- 音高
- 时值
- 音强
- 二维列表 还能怎么玩呢?🤔
- 原来的列表
- 属于一维列表
lst = [1, 2, 3]
- 列表的列表
- 就是 二维列表
lst = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9] ]
lst - 可以 让 二维列表 降维
- 变成 一维列表 吗?
- 列表的列表
- 属于二维列表
- lst 是一个 3*3 的 二维列表
- 通过索引
- 得到了 二维列表的 第0个列表项
- 一维列表 [1, 2, 3]
- 3个列表项 都能索引到 吗?
lst
lst[0]
lst[1]
lst[2]- 得到 三个列表项
- 可以 进一步 索引进去 吗?
lst
lst[0]
lst[0][0]
lst[0][1]
lst[0][2]
- 通过索引找到具体的列表项
- 可以
切片吗?
lst[0]
lst[0][:]
lst[0][0:]
lst[0][1:]
lst[0][:2]
lst[0][1:2]
- 可以再对 a[0] 这个 一维列表
- 进行 切片操作
- 可以 先
切片吗?
- 二维列表先切片
lst[0:]
lst[0:2]
- 得到的
- 还是 这个 二维列表
- 是 按
行切出来的 列表
- 可以 继续 切片 吗?
- 二维列表 再切片
- 得到的 还是二维列表
lst
lst[:]
lst[0:2]
lst[0:2][:1]
lst[0:2][1:]
- 切片 维持原来的维度
- 对 二维列表 来说
- 切片 是 筛选
- 索引 是 降维
- 用负数 进行 索引
- 可以 吗?
lst[-1]
lst[-2]
lst[-3]
- 负数 做索引
- 相当于 倒着数
- 切片 里 能有 负数 吗?
lst[-3:]
lst[-3:-1]
lst[-3:-1][-3:]
lst[-3:-1][-3:-1]
- 负数索引
- 逻辑 和 正数 一样
| 索引表达式 | 含义 |
|---|---|
| -len | 第0个元素 |
| index-len | 第index个元素 |
| -1 | 最后一个元素 |
- 二维列表 有什么 实际的应用吗?
- 话要 一句一句说
- 中间有喘气的 时间
- 音乐里面 也有气口
- 叫做 乐句
- 音乐由 6个小乐句 构成
- 每句结尾 停顿
from mido import Message, MidiFile, MidiTrack
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
# 定义小星星的音符(C 大调)
notes = [
['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4'],
['F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4'],
['G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4'],
['G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4'],
['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4'],
['F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4']
]
# 音符映射到 MIDI 音高
note_map = {
'C4': 60, 'D4': 62, 'E4': 64, 'F4': 65, 'G4': 67, 'A4': 69, 'B4': 71,
'C5': 72
}
# 设定节奏(四分音符时值为 480)
duration = 480
pause_duration = 480 # 句间停顿时间
fade_out_time = 480 # 过渡时间
for phrase in notes:
for note in phrase:
midi_note = note_map[note]
track.append(Message('note_on', note=midi_note, velocity=64, time=0))
track.append(Message('note_off', note=midi_note, velocity=64, time=duration))
# 让停顿过渡自然,使用逐渐减弱的音量
track.append(Message('note_off', note=0, velocity=32, time=fade_out_time))
track.append(Message('note_off', note=0, velocity=0, time=pause_duration - fade_out_time))
mid.save("twinkle_twinkle_star.mid")
print(f"MIDI 文件已保存为 twinkle star")
- 能找出
- 第3个乐句 的 第1个小节吗?
notes = [
['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4'],
['F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4'],
['G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4'],
['G4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4'],
['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4'],
['F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4']
]
- 列表索引 从零开始
- 第3个 乐句 就是
notes[2]
- 再找 第3个乐句 的
- 前四个音符
notes[2][0: 4]
- 第3乐句 的 第1个小节
- 先找 第3乐句
- 再找 前四拍
- 这里面 有啥重复吗?
- 中间 两句重复
print(notes[2] == notes[3])
- 前两句 和 后两句 重复
- 可见 重复的 力量!
print(notes[:2] == notes[-2:])
- 有 三维列表 吗?
import random
from mido import Message, MidiFile, MidiTrack, MetaMessage
import mido
# ====================== 全局配置区 ======================
NOTE_MAP = {
'1': 60, '2': 62, '3': 64, '4': 65, '5': 67, '6': 69, '7': 71, '_5': 55
}
BEAT_VELOCITY = {1: 100, 2: 80, 3: 90, 4: 80}
INSTRUMENT = 1
TICKS_PER_BEAT = 480
TEMPO_BPM = 120 # 播放速度
# ====================== 起承转合:四乐段×两乐句(优化嵌套结构) ======================
# 维度1:乐段(起/承/转/合);维度2:乐句;维度3:小节(音符列表)
MELODY = [
# ---------------------- 【起】乐段 ----------------------
[
# 乐句1:两只老虎(第一遍)(移除多余的外层列表)
[['1', 1], ['2', 1], ['3', 1], ['1', 1]],
# 乐句2:两只老虎(第二遍)
[['1', 1], ['2', 1], ['3', 1], ['1', 1]]
],
# ---------------------- 【承】乐段 ----------------------
[
# 乐句1:跑得快(第一遍)
[['3', 1], ['4', 1], ['5', 2]],
# 乐句2:跑得快(第二遍)
[['3', 1], ['4', 1], ['5', 2]]
],
# ---------------------- 【转】乐段 ----------------------
[
# 乐句1:一只没有眼睛
[['5', 0.5], ['6', 0.5], ['5', 0.5], ['4', 0.5], ['3', 1], ['1', 1]],
# 乐句2:一只没有尾巴
[['5', 0.5], ['6', 0.5], ['5', 0.5], ['4', 0.5], ['3', 1], ['1', 1]]
],
# ---------------------- 【合】乐段 ----------------------
[
# 乐句1:真奇怪(第一遍)
[['2', 1], ['_5', 1], ['1', 2]],
# 乐句2:真奇怪(第二遍)
[['2', 1], ['_5', 1], ['1', 2]]
]
]
# ====================== MIDI生成函数(适配优化后的结构) ======================
def generate_midi():
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
# 设置速度、时间签名、乐器
track.append(MetaMessage('set_tempo', tempo=mido.bpm2tempo(TEMPO_BPM)))
track.append(MetaMessage('time_signature', numerator=4, denominator=4))
track.append(Message('program_change', program=INSTRUMENT, time=0))
# 遍历:乐段 -> 乐句(小节) -> 音符(简化嵌套层级)
for section_idx, section in enumerate(MELODY, start=1):
section_names = ['起', '承', '转', '合']
print(f"\n===== 处理【{section_names[section_idx-1]}】乐段 =====")
for phrase_idx, phrase in enumerate(section, start=1):
print(f" --- 处理【乐句{phrase_idx}】---")
current_bar_beat = 1.0 # 小节内的节拍位置(从1拍开始)
for note_idx, note_info in enumerate(phrase, start=1):
note_name, duration = note_info
midi_note = NOTE_MAP[note_name]
duration_ticks = int(duration * TICKS_PER_BEAT) # 转换为MIDI ticks
# 确定当前节拍对应的力度
beat_pos = int(current_bar_beat)
beat_pos = beat_pos if beat_pos <= 4 else beat_pos % 4
velocity = BEAT_VELOCITY.get(beat_pos, 64)
# 打印音符信息(便于调试)
print(f" 音符{note_idx}:{note_name}({duration}拍)| 节拍={current_bar_beat:.1f} | 力度={velocity}")
# 添加音符的开/关事件
track.append(Message('note_on', note=midi_note, velocity=velocity, time=0))
track.append(Message('note_off', note=midi_note, velocity=0, time=duration_ticks))
# 更新小节内的节拍位置
current_bar_beat += duration
# 保存MIDI文件
mid.save('two_tigers_qichenzhuanhe.mid')
print("\n✅ 起承转合版MIDI生成完成!文件名:two_tigers_qichenzhuanhe.mid")
if __name__ == "__main__":
generate_midi()MELODY = [
# ---------------------- 【起】乐段 ----------------------
[
# 乐句1:两只老虎(第一遍)(移除多余的外层列表)
[['1', 1], ['2', 1], ['3', 1], ['1', 1]],
# 乐句2:两只老虎(第二遍)
[['1', 1], ['2', 1], ['3', 1], ['1', 1]]
],
# ---------------------- 【承】乐段 ----------------------
[
# 乐句1:跑得快(第一遍)
[['3', 1], ['4', 1], ['5', 2]],
# 乐句2:跑得快(第二遍)
[['3', 1], ['4', 1], ['5', 2]]
],
# ---------------------- 【转】乐段 ----------------------
[
# 乐句1:一只没有眼睛
[['5', 0.5], ['6', 0.5], ['5', 0.5], ['4', 0.5], ['3', 1], ['1', 1]],
# 乐句2:一只没有尾巴
[['5', 0.5], ['6', 0.5], ['5', 0.5], ['4', 0.5], ['3', 1], ['1', 1]]
],
# ---------------------- 【合】乐段 ----------------------
[
# 乐句1:真奇怪(第一遍)
[['2', 1], ['_5', 1], ['1', 2]],
# 乐句2:真奇怪(第二遍)
[['2', 1], ['_5', 1], ['1', 2]]
]
]
len(MELODY)- 整个 旋律
- 总共 4段
- 每段 2句
- 想要 随机次序 播放乐段
- 想让旋律列表 的
- 4个乐段的次序
- 随机变化
random.shuffle(MELODY)
print(MELODY)
- 效果
- 这是在
乐段的 层面上 随机- 可以在 乐句的层面上 随机 吗?
- 起承转合 四段
- 每个 都是 两小节重复
- 又见 重复的 力量!
- 乐句 无法随机
- 小节中 的 音符 可以随机吗?
- 音符随机
# ====================== 核心操作:随机打乱指定小节 ======================
# 打乱“乐句1-小节2”的音符顺序(仅打乱音符播放顺序,保留时值)
random.shuffle(MELODY[0][1]) # MELODY[0]=乐句1,MELODY[0][1]=乐句1的第2小节
print(f"【打乱结果】乐句1-小节2的音符顺序:{MELODY[0][1]}")
- 第二小节
- 确实 把音符次序 打乱了
- 音符的要素 可以随机吗?
- 目前音符的 两个要素
- 音高
- 时长
note = ['1', 1]
- 数据类型 和 物理意义 都不相同
- 随机 没有意义 !
- 这次 我们 了解了列表的 嵌套(embedded)
- 列表项 也可以是 列表
- 可以 一直 嵌套下去
-
数字 不只是 简单的数字
- 在
结构中的数字
- 在
-
可以 在结构中
随机- 也可以 在结构中
排序吗?
- 也可以 在结构中
-
下次再说 👋
- 本文来自 oeasy Python 系统教程。
- 想完整、扎实学 Python,
- 搜索 oeasy 即可。






















