Skip to content

Latest commit

 

History

History
553 lines (407 loc) · 13.3 KB

File metadata and controls

553 lines (407 loc) · 13.3 KB
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]}")
  • 第二小节
    • 确实 把音符次序 打乱了

图片描述

  • 音符的要素 可以随机吗?

音符要素随机

  • 目前音符的 两个要素
    1. 音高
    2. 时长
note = ['1', 1]
  • 数据类型 和 物理意义 都不相同
    • 随机 没有意义 !

总结

  • 这次 我们 了解了列表的 嵌套(embedded)
    • 列表项 也可以是 列表
    • 可以 一直 嵌套下去

图片描述

  • 数字 不只是 简单的数字

    • 结构 中的数字
  • 可以 在结构中 随机

    • 也可以 在结构中 排序 吗?
  • 下次再说 👋

  • 配套视频


  • 本文来自 oeasy Python 系统教程。
  • 想完整、扎实学 Python,
  • 搜索 oeasy 即可。