121用列表乘法控制鼓点
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。[专业选修]列表_多维列表运算_列表相加_列表相乘
回忆
- 配套视频
- 赋值的 三种情况
|------------------|-------------|
| 赋值方式 | 核心特点(大白话口诀) |
| = | 共用地址,一改全改 |
| copy() | 浅层独立,深层共用 |
| deepcopy() | 全部独立,互不干扰 |

- 我想复制节奏

- 这里面 有什么说法吗?🤔
直接相加
- 两种节奏小节
-
动词打次
-
动动打次
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2 -
节奏片段
- 3个 动词打次 小节
- 1个 动动打次 小节
- 一首歌
- 有两个 节奏片段
- 将相加结果进行赋值

- 想 对song[0] 直接赋值
直接赋值
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
song[0] = list("次次次次")
- 第一个 小节
- 鼓点变了

- 如果想要 所有的
动词打次都成次次次次呢?
变化
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
song[0][0:] = list("次" * 4)
- 把 直接替换列表项
- 换成了 切片赋值

- 如果 有三首歌
- 都重复 这套节奏 呢?
三首歌
- 三首歌 节奏重复
-
又增了 一层结构
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3 -
三首歌
- 都指向 同一首歌 的 地址

- 想要 只修改
- 第一首歌的 第一小节
修改
# 初始代码
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3
# 直接替换列表
songs[0][0] = list("次次次次")
- 三首歌的第一小节 都修改了

- 我只想修改 第一首的第一小节
浅拷贝
# 初始化原始数据
b1 = list("动次打次")
b2 = list("动动打次")
r1 = [b1] * 3 + [b2]
song = r1 * 2
songs = [song] * 3 # 此时3首歌共享同一个song对象
# 关键步骤:先给第一首歌创建独立的浅拷贝,切断引用
songs[0] = songs[0].copy() # 或 list(songs[0]) / songs[0][:]
# 现在修改第一首歌的第一小节,仅影响第一首
songs[0][0] = list("次次次次")
- 确实只修改 第一首歌的 第一小节

- 但这个结构还是特别乱
- 我想给每个小节
- 都分配独立空间
深拷贝
from copy import deepcopy
# 基础节拍单元(单层列表,无嵌套)
beat_b1 = list("动次打次")
beat_b2 = list("动动打次")
# 用 deepcopy 替代 copy(),效果完全一致
song_verses = [
deepcopy(beat_b1), # 第1小节(独立,无引用)
deepcopy(beat_b1), # 第2小节(独立)
deepcopy(beat_b1), # 第3小节(独立)
deepcopy(beat_b2) # 第4小节(独立)
]
single_song = deepcopy(song_verses * 2)
three_song = deepcopy([single_song]) + deepcopy([single_song]) + deepcopy([single_song])
- 先 完成 基础节奏

- 然后完成一首歌

最后的三首歌
- three_song中的每个小节
- 都有自己独立的位置
- 互不影响
- 但是比较 占用空间

- 但是 pattern当中的 4个小节还是有重复
彻底独立
from copy import deepcopy
# 基础节拍单元(单层列表,无嵌套)
beat_b1 = list("动次打次")
beat_b2 = list("动动打次")
# 用 deepcopy 替代 copy(),效果完全一致
song_verses = [
deepcopy(beat_b1), # 第1小节(独立,无引用)
deepcopy(beat_b1), # 第2小节(独立)
deepcopy(beat_b1), # 第3小节(独立)
deepcopy(beat_b2) # 第4小节(独立)
]
single_song = deepcopy(song_verses) + deepcopy(song_verses)
three_song = deepcopy([single_song]) + deepcopy([single_song]) + deepcopy([single_song])

- 能把这个写成代码吗?
代码
- 三首歌
- 24个小节

import copy
import mido
from mido import Message, MidiFile, MidiTrack
# ===================== 1. 基础节拍定义(修正乐器映射) =====================
# 重新定义4/4拍的"动次打次"乐器映射(按你的要求)
# 动: 低音鼓 (Kick, MIDI 36) - 1拍
# 次: 闭合踩镲 (Hi-Hat, MIDI 42) - 1拍
# 打: 军鼓 (Snare, MIDI 38) - 1拍
beat_mapping = {
'动': [36], # 低音鼓 (Kick)
'次': [42], # 闭合踩镲 (Hi-Hat)
'打': [38] # 军鼓 (Snare)
}
# 基础节拍单元(标准4/4拍,每小节4拍)
beat_b1 = list("动次打次") # 动(1拍)、次(1拍)、打(1拍)、次(1拍)
beat_b2 = list("动动打次") # 动(1拍)、动(1拍)、打(1拍)、次(1拍)
# 用 deepcopy 确保每个小节独立
song_verses = [
copy.deepcopy(beat_b1), # 第1小节(4拍)
copy.deepcopy(beat_b1), # 第2小节(4拍)
copy.deepcopy(beat_b1), # 第3小节(4拍)
copy.deepcopy(beat_b2) # 第4小节(4拍)
]
single_song = copy.deepcopy(song_verses * 2) # 完整单曲(8小节,32拍)
three_song = [
copy.deepcopy(single_song),
copy.deepcopy(single_song),
copy.deepcopy(single_song)
] # 三首连播
# ===================== 2. 生成MIDI文件核心逻辑(修正时序) =====================
def create_beat_midi(beat_sequence, filename="dongci_daci.mid", tempo=120):
"""
生成标准4/4拍的打击乐MIDI文件
:param beat_sequence: 嵌套的节拍列表
:param filename: 输出MIDI文件名
:param tempo: 速度(BPM),120对应每分钟120拍
"""
# 创建MIDI文件和音轨
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
# 设置速度(120 BPM = 500000 微秒/拍)
track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(tempo)))
# 10号通道(索引9)是MIDI标准打击乐通道
track.append(Message('program_change', channel=9, program=0, time=0))
# 4/4拍的核心:每拍时长 = 480 ticks(MIDI标准四分音符)
tick_per_beat = 480
# 扁平化嵌套的节拍序列(转为一维列表,确保逐拍处理)
flat_beats = []
for song in beat_sequence:
for bar in song: # bar = 小节
flat_beats.extend(bar)
# 生成MIDI音符(关键修正:确保每拍严格占480 ticks)
# 初始时间偏移为0
current_time = 0
for beat_char in flat_beats:
if beat_char in beat_mapping:
note = beat_mapping[beat_char][0] # 每个节拍对应一个乐器
# 音符开启(note_on):在current_time时刻触发
track.append(Message(
'note_on',
channel=9,
note=note,
velocity=80, # 音量(64-100为常用范围)
time=current_time
))
# 音符关闭(note_off):间隔1拍(480 ticks)后释放
track.append(Message(
'note_off',
channel=9,
note=note,
velocity=80,
time=tick_per_beat
))
# 重置时间偏移,确保下一拍从0开始
current_time = 0
# 保存MIDI文件
mid.save(filename)
print(f"标准4/4拍MIDI文件已生成:{filename}")
# ===================== 3. 执行生成 =====================
if __name__ == "__main__":
# 生成三首连播的标准4/4拍MIDI
create_beat_midi(three_song, "three_songs_4_4_beat.mid", tempo=120)
新想法
- 想要 闭擦踩满

闭擦踩满
import copy
import mido
from mido import Message, MidiFile, MidiTrack
# ===================== 1. 基础节拍定义(重新设计数据结构) =====================
# 定义MIDI打击乐音符编号
KICK = 36 # 底鼓
SNARE = 38 # 军鼓
HI_HAT = 42 # 闭合踩镲
# 重新定义4/4拍的基础小节结构(完全按你的要求)
# 第0拍: kick + hh | 第1拍: hh | 第2拍: snare + hh | 第3拍: hh
basic_bar = [
[KICK, HI_HAT], # 第0拍(动):底鼓+踩镲
[HI_HAT], # 第1拍(次):仅踩镲
[SNARE, HI_HAT], # 第2拍(打):军鼓+踩镲
[HI_HAT] # 第3拍(次):仅踩镲
]
# 变体小节(保留差异化,第0/1拍都是kick+hh)
variant_bar = [
[KICK, HI_HAT], # 第0拍:底鼓+踩镲
[KICK, HI_HAT], # 第1拍:底鼓+踩镲(变体)
[SNARE, HI_HAT], # 第2拍:军鼓+踩镲
[HI_HAT] # 第3拍:仅踩镲
]
# 构建歌曲结构(用deepcopy确保每个小节独立)
song_verses = [
copy.deepcopy(basic_bar), # 第1小节
copy.deepcopy(basic_bar), # 第2小节
copy.deepcopy(basic_bar), # 第3小节
copy.deepcopy(variant_bar) # 第4小节(变体)
]
single_song = copy.deepcopy(song_verses * 2) # 完整单曲(8小节)
three_song = [
copy.deepcopy(single_song),
copy.deepcopy(single_song),
copy.deepcopy(single_song)
] # 三首连播
# ===================== 2. 生成MIDI文件核心逻辑(适配新结构) =====================
def create_beat_midi(beat_sequence, filename="dongci_daci.mid", tempo=120):
"""
生成标准4/4拍的打击乐MIDI文件(适配新的拍子列表结构)
:param beat_sequence: 嵌套的节拍列表(每个拍子是乐器编号列表)
:param filename: 输出MIDI文件名
:param tempo: 速度(BPM)
"""
# 创建MIDI文件和音轨
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
# 设置速度(120 BPM = 500000 微秒/拍)
track.append(mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(tempo)))
# 10号通道(索引9)是MIDI标准打击乐通道
track.append(Message('program_change', channel=9, program=0, time=0))
# 4/4拍核心:每拍时长 = 480 ticks(MIDI标准四分音符)
tick_per_beat = 480
# 扁平化嵌套的节拍序列(转为一维的"拍子列表")
flat_beats = []
for song in beat_sequence:
for bar in song: # bar = 小节(包含4个拍子)
for beat in bar: # beat = 拍子(包含1个/多个乐器)
flat_beats.append(beat)
# 生成MIDI音符(适配新结构:直接遍历乐器列表)
current_time = 0
for beat_instruments in flat_beats:
# 触发当前拍子的所有乐器(note_on)
for instrument in beat_instruments:
track.append(Message(
'note_on',
channel=9,
note=instrument,
velocity=80, # 音量(适中)
time=current_time
))
# 释放当前拍子的所有乐器(note_off),间隔1拍
for instrument in beat_instruments:
track.append(Message(
'note_off',
channel=9,
note=instrument,
velocity=80,
time=tick_per_beat if instrument == beat_instruments[0] else 0
))
# 重置时间偏移,确保下一拍从0开始
current_time = 0
# 保存MIDI文件
mid.save(filename)
print(f"标准4/4拍MIDI文件已生成:{filename}")
# ===================== 3. 执行生成 =====================
if __name__ == "__main__":
# 生成三首连播的MIDI文件
create_beat_midi(three_song, "three_songs_4_4_full_hh.mid", tempo=120)
缝合(zip)
- 列表还有一种运算方式
-
叫做缝合(zip)
students = ["oeasy", "o2z", "o3z"]
math = [95, 96, 97]
chinese = [91, 92, 93]
score = list(zip(students, math, chinese))
print(score) -
zip 可以 把 不同列表
- 缝到一起

- 同样位置的元素
- 缝到一个新元素里面
查询帮助

- 查询关于zip的帮助
- help(zip)

- 缝合出来的列表可以用lambda排序吗?
排序
sorted_by_chinese = sorted(score, key=lambda x: x[2])
sorted_by_chinese
- 确实可以按照 语文成绩 排序

总结
- 这次我们 玩了节奏
-
可以 让节奏成为套路
-
也可以 让节奏独立空间
students = ["oeasy", "o2z", "o3z"]
math = [95, 96, 97]
chinese = [91, 92, 93]
score = list(zip(students, math, chinese))
print(score) -
缝合操作
- 把列表里 同样位置的元素
- 缝合 在一起

- 缝合里 的列表项
- 怎么不是
中括号包裹的呢??🤔