首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >超酷!用Python写回文音乐,3步搞定你的MIDI创作处女作

超酷!用Python写回文音乐,3步搞定你的MIDI创作处女作

原创
作者头像
风一样的男子
发布于 2025-11-05 15:18:00
发布于 2025-11-05 15:18:00
9110
举报
文章被收录于专栏:编程教程编程教程

​免费编程软件「python+pycharm」

链接:https://pan.quark.cn/s/48a86be2fdc0

音乐与代码的碰撞能产生什么火花?当数学中的回文结构遇上MIDI音乐创作,我们可以用Python生成一首正着听反着听都相同的奇妙旋律。无需乐理基础,无需专业设备,只需30分钟,你就能完成人生第一首AI辅助创作的音乐作品。

一、回文音乐:代码与艺术的完美对称

1.1 什么是回文音乐?

回文音乐(Palindrome Music)指正序和倒序播放完全相同的音乐片段。就像"上海自来水来自海上"这句话,无论从左读还是从右读都完全一致。在音乐中,这种结构会创造出独特的听觉体验——前半段逐渐展开,后半段自然收束,形成完美的闭环。

历史上许多作曲家都尝试过这种形式:巴赫的《螃蟹卡农》可以上下颠倒演奏;韦伯恩的《钢琴变奏曲》第二乐章是严格的回文结构。现在,我们用Python就能轻松实现这种高级音乐技巧。

1.2 技术实现原理

实现回文音乐的关键在于:

  1. 音符序列对称:第1个音符对应最后1个,第2个对应倒数第2个,以此类推
  2. 节奏对称:前半段的节奏型在后半段反向重现
  3. MIDI协议支持:用数字信号精确控制每个音符的音高、时值和力度

Python的mido库能完美处理MIDI协议,配合列表操作就能轻松构建回文结构。

二、环境准备:3分钟搭建创作工坊

2.1 安装必要库

打开终端执行以下命令:

代码语言:javascript
复制
pip install mido pretty_midi numpy
  • mido:处理MIDI消息的核心库
  • pretty_midi:简化MIDI文件读写
  • numpy:处理数值计算

2.2 验证安装

运行这段测试代码:

代码语言:javascript
复制
import mido
print(f"检测到MIDI端口:{mido.get_output_names()}")

如果看到类似['Microsoft GS Wavetable Synth']的输出,说明系统已准备好创作。

2.3 备用方案:虚拟MIDI设备

若没有物理MIDI输出设备,可安装虚拟MIDI端口:

  • Windows:安装 loopMIDI
  • Mac:使用内置的IAC Driver
  • Linux:安装snd-virmidi模块

三、第一步:生成基础旋律线

3.1 定义音符参数

用Python列表存储音符信息,每个元素是(音高, 时值, 力度)的元组:

代码语言:javascript
复制
# C大调音阶(音高对应MIDI编号:60=中央C)
scale = [60, 62, 64, 65, 67, 69, 71, 72]

# 生成8个音符的简单旋律
base_melody = [
    (scale[0], 480, 80),   # 音符1:C4,四分音符,力度80
    (scale[2], 240, 70),   # 音符2:E4,八分音符,力度70
    (scale[4], 240, 75),   # 音符3:G4,八分音符,力度75
    (scale[5], 480, 90),   # 音符4:A4,四分音符,力度90
    (scale[3], 240, 65),   # 音符5:F4,八分音符,力度65
    (scale[1], 240, 70),   # 音符6:D4,八分音符,力度70
    (scale[0], 960, 100)   # 音符7:C4,全音符,力度100
]

3.2 创建回文结构

将基础旋律反转并调整力度:

代码语言:javascript
复制
def create_palindrome(melody):
    # 反转音符序列(保留第一个音符作为中心点)
    reversed_part = melody[-2::-1]  # 从倒数第二个开始反转
    
    # 调整后半部分力度(可选:减弱处理)
    palindrome = melody.copy()
    for i, note in enumerate(reversed_part):
        new_note = (note[0], note[1], note[2] * 0.8)
        palindrome.append(new_note)
    
    return palindrome

full_melody = create_palindrome(base_melody)

3.3 可视化验证

用matplotlib绘制音符分布:

代码语言:javascript
复制
import matplotlib.pyplot as plt

pitches = [note[0] for note in full_melody]
durations = [note[1]/100 for note in full_melody]  # 转换为秒

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.stem(range(len(pitches)), pitches)
plt.title("Pitch Sequence")

plt.subplot(1, 2, 2)
plt.bar(range(len(durations)), durations)
plt.title("Duration Sequence")
plt.show()

四、第二步:构建完整MIDI文件

4.1 创建MIDI轨道

使用pretty_midi构建完整结构:

代码语言:javascript
复制
import pretty_midi

def create_midi(melody, tempo=120, output_file="palindrome.mid"):
    # 创建MIDI对象
    pm = pretty_midi.PrettyMIDI(initial_tempo=tempo)
    
    # 创建钢琴轨道
    piano_program = pretty_midi.instrument_name_to_program('Acoustic Grand Piano')
    piano = pretty_midi.Instrument(program=piano_program)
    
    # 添加音符
    current_time = 0
    for pitch, duration, velocity in melody:
        # 将相对时值转换为绝对时间
        note_length = duration / 1000  # 转换为秒
        note = pretty_midi.Note(
            velocity=int(velocity),
            pitch=int(pitch),
            start=current_time,
            end=current_time + note_length
        )
        piano.notes.append(note)
        current_time += note_length
    
    pm.instruments.append(piano)
    pm.write(output_file)
    print(f"MIDI文件已保存:{output_file}")

create_midi(full_melody)

4.2 添加和声层次

让音乐更丰富:

代码语言:javascript
复制
def add_harmony(melody, interval=3):
    harmony = []
    for pitch, duration, velocity in melody:
        # 在指定音程上添加和声音符
        harmony_pitch = pitch + interval
        harmony.append((harmony_pitch, duration, velocity * 0.7))
    return harmony

harmony_track = add_harmony(full_melody)
create_midi(harmony_track, output_file="harmony.mid")

4.3 合并多轨道

创建包含主旋律和和声的完整MIDI:

代码语言:javascript
复制
def merge_tracks(*tracks):
    pm = pretty_midi.PrettyMIDI(initial_tempo=120)
    
    for i, track_data in enumerate(tracks):
        if i == 0:
            program = pretty_midi.instrument_name_to_program('Acoustic Grand Piano')
        else:
            program = pretty_midi.instrument_name_to_program('String Ensemble 1')
            
        instrument = pretty_midi.Instrument(program=program)
        current_time = 0
        
        for pitch, duration, velocity in track_data:
            note_length = duration / 1000
            note = pretty_midi.Note(
                velocity=int(velocity),
                pitch=int(pitch),
                start=current_time,
                end=current_time + note_length
            )
            instrument.notes.append(note)
            current_time += note_length
        
        pm.instruments.append(instrument)
    
    return pm

full_composition = merge_tracks(full_melody, add_harmony(full_melody))
full_composition.write("complete_palindrome.mid")

五、第三步:进阶创作技巧

5.1 动态节奏变化

让回文结构更自然:

代码语言:javascript
复制
import random

def dynamic_palindrome(melody, rhythm_variation=0.2):
    palindrome = melody.copy()
    reversed_part = melody[-2::-1]
    
    for i, note in enumerate(reversed_part):
        # 随机调整时值(±20%)
        original_duration = note[1]
        variation = original_duration * rhythm_variation * (random.random() * 2 - 1)
        new_duration = max(10, original_duration + variation)  # 最小时值10ms
        
        palindrome.append((
            note[0],
            new_duration,
            note[2] * (0.7 + random.random() * 0.3)  # 力度随机变化
        ))
    
    return palindrome

dynamic_melody = dynamic_palindrome(base_melody, rhythm_variation=0.3)

5.2 多声部回文

创建四个声部的复杂结构:

代码语言:javascript
复制
def multi_voice_palindrome(base_melody, voices=4):
    all_voices = [base_melody.copy()]
    
    for _ in range(1, voices):
        # 每个声部音高偏移
        offset = random.randint(-5, 5)
        transposed = [(p + offset, d, v) for p, d, v in base_melody]
        all_voices.append(transposed)
    
    # 为每个声部创建回文
    full_composition = []
    for voice in all_voices:
        palindrome = create_palindrome(voice)
        full_composition.extend(palindrome)
    
    return full_composition

complex_piece = multi_voice_palindrome(base_melody, voices=3)

5.3 算法生成基础旋律

完全用代码创作旋律:

代码语言:javascript
复制
def generate_random_melody(length=8, key_scale=None):
    if key_scale is None:
        key_scale = [60, 62, 64, 65, 67, 69, 71]  # C大调
    
    melody = []
    current_pitch = key_scale[0]
    
    for _ in range(length):
        # 随机选择下一个音符(70%概率继续当前音阶方向)
        if len(melody) > 0 and random.random() < 0.7:
            last_pitch = melody[-1][0]
            diff = last_pitch - current_pitch
            current_pitch = last_pitch + (diff // abs(diff) if diff != 0 else 1)
            # 确保仍在音阶内
            if current_pitch not in key_scale:
                current_pitch = max(min(current_pitch, key_scale[-1]), key_scale[0])
        else:
            current_pitch = random.choice(key_scale)
        
        # 随机时值(四分音符为主)
        duration = random.choice([240, 240, 240, 480, 720])  # 偏重短音符
        velocity = random.randint(60, 100)
        
        melody.append((current_pitch, duration, velocity))
    
    return melody

algo_melody = generate_random_melody(length=12)

六、作品展示与优化

6.1 播放MIDI文件

使用mido播放创作成果:

代码语言:javascript
复制
def play_midi(file_path):
    with open(file_path, 'rb') as f:
        mid = mido.MidiFile(f)
    
    port = mido.open_output()
    for msg in mid.play():
        port.send(msg)

play_midi("complete_palindrome.mid")

6.2 转换为音频

推荐使用FluidSynth将MIDI转为WAV:

代码语言:javascript
复制
# 安装FluidSynth
sudo apt install fluidsynth  # Linux
brew install fluidsynth      # Mac

# 转换命令
fluidsynth -i soundfont.sf2 complete_palindrome.mid -F output.wav -r 44100

6.3 常见问题解决

Q1:生成的MIDI文件无法播放? A:检查是否安装了MIDI合成器。Windows用户可安装 CoolSoft VirtualMIDISynth,Mac用户使用内置的DLS Music Device。

Q2:如何调整音乐速度? A:修改PrettyMIDI的initial_tempo参数(默认120BPM),数值越大速度越快。

Q3:想用真实乐器音色? A:下载高质量音源如​​​​​​​ SFZ格式的免费音源,或使用商业音源如EastWest Quantum Leap。

七、创作灵感扩展

7.1 数学之美

回文结构本质是数学对称,尝试:

  • 斐波那契数列生成节奏型
  • 分形算法创建自相似结构
  • 黄金分割比例安排乐句长度

7.2 跨学科融合

结合其他艺术形式:

  • 用诗歌生成回文歌词
  • 根据建筑结构创作对应音乐
  • 用股票走势数据生成旋律

7.3 交互式创作

开发Web应用让用户实时生成回文音乐:

代码语言:javascript
复制
from flask import Flask, send_file
import io

app = Flask(__name__)

@app.route('/create')
def create_music():
    # 这里调用之前的创作函数
    melody = generate_random_melody()
    full = create_palindrome(melody)
    pm = pretty_midi.PrettyMIDI(initial_tempo=100)
    # ...构建MIDI对象...
    
    buffer = io.BytesIO()
    pm.write_to_fp(buffer)
    buffer.seek(0)
    
    return send_file(buffer, mimetype='audio/midi', as_attachment=True, download_name='dynamic_palindrome.mid')

if __name__ == '__main__':
    app.run(debug=True)

结语

从基础回文结构到复杂多声部创作,我们用Python解锁了音乐算法的新可能。这种创作方式不是要取代人类作曲家,而是提供新的灵感来源——就像照相机没有取代绘画,反而拓展了艺术表达的边界。现在打开你的IDE,让代码奏响数字时代的回文交响乐吧!

​

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、回文音乐:代码与艺术的完美对称
    • 1.1 什么是回文音乐?
    • 1.2 技术实现原理
  • 二、环境准备:3分钟搭建创作工坊
    • 2.1 安装必要库
    • 2.2 验证安装
    • 2.3 备用方案:虚拟MIDI设备
  • 三、第一步:生成基础旋律线
    • 3.1 定义音符参数
    • 3.2 创建回文结构
    • 3.3 可视化验证
  • 四、第二步:构建完整MIDI文件
    • 4.1 创建MIDI轨道
    • 4.2 添加和声层次
    • 4.3 合并多轨道
  • 五、第三步:进阶创作技巧
    • 5.1 动态节奏变化
    • 5.2 多声部回文
    • 5.3 算法生成基础旋律
  • 六、作品展示与优化
    • 6.1 播放MIDI文件
    • 6.2 转换为音频
    • 6.3 常见问题解决
  • 七、创作灵感扩展
    • 7.1 数学之美
    • 7.2 跨学科融合
    • 7.3 交互式创作
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档