mcpField
C
code-to-music

code-to-music

github.com/cam10001110101创意与媒体844 次安装26 周前更新
代码仓库

关于 code-to-music

code-to-music 是一款按请求编写程序化音乐的 skill,提供电子与传统音乐渲染流程,并指导生成 MP3 文件。

主要功能

  1. 01算法作曲指导
  2. 02电子与传统音乐流程
  3. 03使用 mido 分配 MIDI 乐器
  4. 04MIDI 渲染与音频校验

适用场景

  1. 01根据需求创作电子音乐
  2. 02制作原声或管弦乐编曲
  3. 03将 MIDI 乐曲渲染为 MP3
  4. 04检查并校验 MIDI 或音频文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cam10001110101/claude-skills-base --skill code-to-music

安装后在 Claude Code 中输入/code-to-music确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

electronic-music-pipeline.md
install.sh
requirements.txt
traditional-music-pipeline.md
scripts/
__init__.py
audio_validate.py
midi_render.py
midi_utils.py

详细介绍

此技能提供的是用于音乐创作的工具和模式,而非预制方案。应使用 intelligence 和 music21 库,根据用户请求动态创作。

核心原则:编写自定义代码,以算法方式创作音乐,而不是调用包含硬编码旋律的函数。

安装与设置

快速安装

运行自动安装程序完成全部设置:install.sh。此程序会安装所有系统依赖项和 Python 包,并验证安装结果。

**注意:**安装脚本最后可能会显示“error: externally-managed-environment”消息。这些消息是预期现象,可以放心忽略——依赖项已经安装。如果出现此类消息,说明安装已成功。

可用的 SoundFont

传统流程(管弦乐/原声):

  • /usr/share/sounds/sf2/FluidR3_GM.sf2(141MB,General MIDI 音色库——用于高品质管弦乐/原声采样)
  • /usr/share/sounds/sf2/default.sf2(指向最佳可用音色库的符号链接)
  • ⚠️ 不要使用 TimGM6mb.sf2——音质较差,吉他听起来像电子琴/钢琴

电子流程:

  • 使用与传统音乐相同的 FluidR3_GM.sf2 音色库
  • 区别在于所选的 GM 音色编号(使用合成器音色 38-39、80-95,而非管弦乐音色)
核心概念
  • 始终创建可下载的 MP3 文件(不要使用 HTML 播放器)
  • 可以使用 music21.instrument 类以便于操作:instrument.Violin()、instrument.Violoncello()、instrument.Piano()、instrument.Trumpet() 等。
  • 重要:导出后始终使用 mido 设置 MIDI 音色编号——music21 的乐器类无法可靠地导出 program_change 消息。请参阅下方的 mido 工作流。
  • 以编程方式生成音符——避免使用硬编码序列

选择音乐生成流程

此技能支持两种针对不同音乐风格优化的渲染流程。仔细阅读用户请求,并通过阅读相应的后续文件来选择正确的流程。

决策过程

从用户请求中识别以下信息:

  • 流派关键词(house、techno、古典、管弦乐、reggae)
  • 乐器相关表述(合成器还是原声乐器)
  • 音乐风格描述(电子/DJ 还是传统/原声)
  • 提及的艺术家(DJ 还是古典作曲家)
电子流程

当请求包含以下内容时,使用 electronic-music-pipeline.md:

  • 流派:House、techno、trance、EDM、电子舞曲、电子氛围音乐、acid house、deep house、俱乐部音乐、DJ 套曲
  • 乐器:合成器、合成贝斯/铺底音色/主音、808 鼓、电子鼓、supersaw 主音、超低音
  • 背景信息:提及 DJ(Keinemusik、Black Coffee、Avicii、Swedish House Mafia)、BPM 120-140、“适合俱乐部播放”、“适合跳舞”
  • 声音描述:“厚重的合成器音色”、“嗡鸣的主音”、“电子风格”、“以合成器为主”

工作原理:

  • 使用实时合成(不使用音色库)
  • 使用 DSP 即时合成鼓、贝斯、铺底音色和主音
  • 流派预设会优化合成参数(deep_house、techno、trance、ambient、acid_house)
  • 根据频率感知的混音会自动平衡低、中、高频
  • 最适合:制作逼真的电子音色和现代 EDM 音乐

→ 详细说明请参阅 electronic-music-pipeline.md


传统音乐流水线

当请求包含以下内容时,使用 traditional-music-pipeline.md:

  • 流派:古典、管弦乐、爵士、布鲁斯、摇滚、乡村、民谣、雷鬼、斯卡、交响乐、室内乐
  • 乐器:小提琴、大提琴、小号、长笛、钢琴、原声吉他、原声贝斯、铜管乐合奏、弦乐四重奏、管风琴(雷鬼/福音音乐)
  • 上下文:提及作曲家(Mozart、Beethoven、Bach)、古典时期(巴洛克、浪漫主义)、「不插电」、「原声版本」
  • 声音描述:「传统」、「原声」、「管弦乐」

工作方式:

  • 使用预录制的管弦乐采样(音色库)
  • 使用包含 128 种 General MIDI 乐器的 FluidR3_GM.sf2 音色库
  • 适用于逼真的管弦乐/原声乐器
  • 最适合:古典、爵士、雷鬼、摇滚、传统音乐

→ 详细说明请参阅 traditional-music-pipeline.md


不确定时
  • 混合请求或流派不明确时,默认使用传统流水线
  • 如果请求可能适用于任一流水线,请向用户确认
这为何重要
  • 使用错误的流水线 = 音质不佳(技术上可以运行,但听起来很糟)
  • 电子音乐流水线:实时合成,生成原汁原味的电子音效
  • 传统音乐流水线:使用预录制采样,生成逼真的管弦乐/原声效果

可用脚本

所有脚本均位于 ./scripts:

传统音乐渲染:

  • midi_inventory.py - 从任意 MIDI 文件提取完整结构并转换为 JSON 格式
  • midi_render.py - 使用带动态范围压缩的 FluidSynth,将 JSON 音乐结构渲染为 MP3
  • midi_utils.py - MIDI 实用函数(提取鼓声、获取 BPM 等)

实用工具:

  • audio_validate.py - 验证音频文件的质量和格式

注意:电子音乐和传统音乐使用相同的渲染流水线(FluidSynth + FluidR3_GM.sf2)。两者的区别在于所选用的 GM 音色。

乐理参考

完整 General MIDI 乐器映射(音色编号 0-127)

重要:music21 无法可靠地导出 program_change 消息。导出后,必须始终使用 mido 设置音色编号,即使是萨克斯风、吉他或贝斯等传统乐器也不例外。否则,所有音轨都会默认使用钢琴(音色编号 0)。

合成用乐器分类: 创作电子音乐时,根据乐器的声音特性进行分类,以实现恰当的合成:

  • 吉他(24-31):拨弦起音、明亮音色、中等延音——需要与铺底音色采用不同的合成方式
  • 贝斯(32-39):低频基音、次低音能量、短促起音
  • 弦乐(40-47):持续拉奏、丰富泛音、平滑连奏
  • 铜管(56-63):明亮起音、持续音色、有力投射
  • 簧片乐器/木管乐器(64-79):气声起音、自然音色、富有表现力的力度变化
  • 合成器主音(80-87):明亮、突出、强劲——专为旋律设计
  • 合成器铺底音色(88-95):柔和起音、长延音、富有氛围感——专为背景设计
  • 民族/打击乐器(104-119):拨弦或敲击音色,具有独特音色
# Piano (0-7)
0: "Acoustic Grand Piano"
1: "Bright Acoustic Piano"
2: "Electric Grand Piano"
3: "Honky-tonk Piano"
4: "Electric Piano 1"
5: "Electric Piano 2"
6: "Harpsichord"
7: "Clavinet"

# Chromatic Percussion (8-15)
8: "Celesta"
9: "Glockenspiel"
10: "Music Box"
11: "Vibraphone"
12: "Marimba"
13: "Xylophone"
14: "Tubular Bells"
15: "Dulcimer"

# Organ (16-23)
16: "Drawbar Organ"
17: "Percussive Organ"
18: "Rock Organ"
19: "Church Organ"
20: "Reed Organ"
21: "Accordion"
22: "Harmonica"
23: "Tango Accordion"

# Guitar (24-31)
24: "Acoustic Guitar (nylon)"
25: "Acoustic Guitar (steel)"
26: "Electric Guitar (jazz)"
27: "Electric Guitar (clean)"
28: "Electric Guitar (muted)"
29: "Overdriven Guitar"
30: "Distortion Guitar"
31: "Guitar Harmonics"

# Bass (32-39)
32: "Acoustic Bass"
33: "Electric Bass (finger)"
34: "Electric Bass (pick)"
35: "Fretless Bass"
36: "Slap Bass 1"
37: "Slap Bass 2"
38: "Synth Bass 1"
39: "Synth Bass 2"

# Strings (40-47)
40: "Violin"
41: "Viola"
42: "Cello"
43: "Contrabass"
44: "Tremolo Strings"
45: "Pizzicato Strings"
46: "Orchestral Harp"
47: "Timpani"

# Ensemble (48-55)
48: "String Ensemble 1"
49: "String Ensemble 2"
50: "Synth Strings 1"
51: "Synth Strings 2"
52: "Choir Aahs"
53: "Voice Oohs"
54: "Synth Voice"
55: "Orchestra Hit"

# Brass (56-63)
56: "Trumpet"
57: "Trombone"
58: "Tuba"
59: "Muted Trumpet"
60: "French Horn"
61: "Brass Section"
62: "Synth Brass 1"
63: "Synth Brass 2"

# Reed (64-71)
64: "Soprano Sax"
65: "Alto Sax"
66: "Tenor Sax"
67: "Baritone Sax"
68: "Oboe"
69: "English Horn"
70: "Bassoon"
71: "Clarinet"

# Pipe (72-79)
72: "Piccolo"
73: "Flute"
74: "Recorder"
75: "Pan Flute"
76: "Blown Bottle"
77: "Shakuhachi"
78: "Whistle"
79: "Ocarina"

# Synth Lead (80-87)
80: "Lead 1 (square)"
81: "Lead 2 (sawtooth)"
82: "Lead 3 (calliope)"
83: "Lead 4 (chiff)"
84: "Lead 5 (charang)"
85: "Lead 6 (voice)"
86: "Lead 7 (fifths)"
87: "Lead 8 (bass + lead)"

# Synth Pad (88-95)
88: "Pad 1 (new age)"
89: "Pad 2 (warm)"
90: "Pad 3 (polysynth)"
91: "Pad 4 (choir)"
92: "Pad 5 (bowed)"
93: "Pad 6 (metallic)"
94: "Pad 7 (halo)"
95: "Pad 8 (sweep)"

# Synth Effects (96-103)
96: "FX 1 (rain)"
97: "FX 2 (soundtrack)"
98: "FX 3 (crystal)"
99: "FX 4 (atmosphere)"
100: "FX 5 (brightness)"
101: "FX 6 (goblins)"
102: "FX 7 (echoes)"
103: "FX 8 (sci-fi)"

# Ethnic (104-111)
104: "Sitar"
105: "Banjo"
106: "Shamisen"
107: "Koto"
108: "Kalimba"
109: "Bag pipe"
110: "Fiddle"
111: "Shanai"

# Percussive (112-119)
112: "Tinkle Bell"
113: "Agogo"
114: "Steel Drums"
115: "Woodblock"
116: "Taiko Drum"
117: "Melodic Tom"
118: "Synth Drum"
119: "Reverse Cymbal"

# Sound Effects (120-127)
120: "Guitar Fret Noise"
121: "Breath Noise"
122: "Seashore"
123: "Bird Tweet"
124: "Telephone Ring"
125: "Helicopter"
126: "Applause"
127: "Gunshot"
完整鼓组映射(MIDI 通道 10,音符 35-81)

鼓声使用音符编号来区分不同音色,而非音高。必须使用通道 10(从 0 开始计数时为 9)。

# Bass Drums
35: "Acoustic Bass Drum"
36: "Bass Drum 1"  # Most common kick

# Snares
38: "Acoustic Snare"  # Standard snare
40: "Electric Snare"

# Toms
41: "Low Floor Tom"
43: "High Floor Tom"
45: "Low Tom"
47: "Low-Mid Tom"
48: "Hi-Mid Tom"
50: "High Tom"

# Hi-Hats
42: "Closed Hi-Hat"  # Most used
44: "Pedal Hi-Hat"
46: "Open Hi-Hat"

# Cymbals
49: "Crash Cymbal 1"
51: "Ride Cymbal 1"
52: "Chinese Cymbal"
53: "Ride Bell"
55: "Splash Cymbal"
57: "Crash Cymbal 2"
59: "Ride Cymbal 2"

# Percussion
37: "Side Stick"
39: "Hand Clap"
54: "Tambourine"
56: "Cowbell"
58: "Vibraslap"
60: "Hi Bongo"
61: "Low Bongo"
62: "Mute Hi Conga"
63: "Open Hi Conga"
64: "Low Conga"
65: "High Timbale"
66: "Low Timbale"
67: "High Agogo"
68: "Low Agogo"
69: "Cabasa"
70: "Maracas"
71: "Short Whistle"
72: "Long Whistle"
73: "Short Guiro"
74: "Long Guiro"
75: "Claves"
76: "Hi Wood Block"
77: "Low Wood Block"
78: "Mute Cuica"
79: "Open Cuica"
80: "Mute Triangle"
81: "Open Triangle"
如何使用任意乐器(mido 工作流)

关键规则:即使使用 instrument.TenorSaxophone() 或 instrument.AcousticGuitar() 等乐器类,music21 也无法可靠地导出 program_change 消息。将内容导出为 MIDI 后,必须始终使用 mido 手动插入 program_change 消息。否则,所有音轨听起来都会像钢琴(program 0)。

常见错误:以为 sax_part.insert(0, instrument.TenorSaxophone()) 会自动让音轨听起来像萨克斯管。事实并非如此!仍然必须使用 mido 设置音色编号。

设置乐器的辅助函数:

from mido import Message

def set_track_instrument(track, program):
    """Insert a program_change message at the beginning of a MIDI track."""
    # Find position after all meta messages (track_name, etc.)
    insert_pos = 0
    for j, msg in enumerate(track):
        if not msg.is_meta:  # Insert before first non-meta message
            insert_pos = j
            break
    else:
        # If all messages are meta, insert at end
        insert_pos = len(track)
    track.insert(insert_pos, Message('program_change', program=program, time=0))

# Usage after loading MIDI with mido:
# set_track_instrument(mid.tracks[2], 33)  # Set track 2 to Electric Bass

步骤 1:使用 music21 作曲(使用占位符或跳过乐器设置)

from music21 import stream, note, chord, tempo

score = stream.Score()

# Create parts - don't worry about instrument assignment yet
synth_lead = stream.Part()
synth_pad = stream.Part()
bass = stream.Part()

# Add your notes/chords using .insert() with explicit timing
# CRITICAL: Always use .insert(offset, note) not .append(note)
offset = 0.0
synth_lead.insert(offset, note.Note('E5', quarterLength=1.0))
offset += 1.0
synth_lead.insert(offset, note.Note('G5', quarterLength=1.0))
# ... compose your music with .insert()

score.append(synth_lead)
score.append(synth_pad)
score.append(bass)

# Export to MIDI
midi_path = # define a output path
score.write('midi', fp=midi_path)

步骤 2:检查 MIDI 结构并使用 mido 分配乐器

from mido import MidiFile, Message

mid = MidiFile(midi_path)

# CRITICAL: DO NOT assume track numbers! Inspect the MIDI file first.
# Print track structure to see what tracks exist
print(f"Total tracks: {len(mid.tracks)}")
for i, track in enumerate(mid.tracks):
    note_count = sum(1 for msg in track if msg.type == 'note_on' and msg.velocity > 0)
    print(f"Track {i}: {note_count} notes")

# Helper function to set instruments
def set_track_instrument(track, program):
    """Insert a program_change message at the beginning of a MIDI track."""
    insert_pos = 0
    for j, msg in enumerate(track):
        if not msg.is_meta:
            insert_pos = j
            break
    else:
        insert_pos = len(track)
    track.insert(insert_pos, Message('program_change', program=program, time=0))

# Based on inspection, assign instruments to the correct tracks
# Example: if [drums, bass, guitar] were appended and inspection shows tracks 1, 2, 3 have notes:
set_track_instrument(mid.tracks[1], 80)  # First part - Square Lead
set_track_instrument(mid.tracks[2], 88)  # Second part - Pad
set_track_instrument(mid.tracks[3], 38)  # Third part - Bass

mid.save(midi_path)

步骤 3:对于鼓,也要将通道设置为 9(第 10 通道)

# If track is drums, set ALL messages to channel 9
for i, track in enumerate(mid.tracks):
    if i == 1:  # This is the drum track
        for msg in track:
            if hasattr(msg, 'channel'):
                msg.channel = 9  # Channel 10 in 1-indexed

步骤 4:渲染为音频

from midi2audio import FluidSynth
from pydub import AudioSegment

fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')
wav_path = # define a path here
fs.midi_to_audio(midi_path, wav_path)

audio = AudioSegment.from_wav(wav_path)
mp3_path = # define a path here
audio.export(mp3_path, format='mp3', bitrate='192k')
常见和弦进行与风格
# Standard Progressions (Roman numerals)
"pop": ["I", "V", "vi", "IV"]           # C-G-Am-F (Journey, Adele)
"epic": ["i", "VI", "III", "VII"]       # Am-F-C-G (Epic trailer music)
"sad": ["i", "VI", "iv", "V"]           # Am-F-Dm-E (Melancholic)
"jazz": ["ii", "V", "I", "vi"]          # Dm-G-C-Am (Jazz standard)
"classical": ["I", "IV", "V", "I"]      # C-F-G-C (Classical cadence)
"blues": ["I", "I", "I", "I", "IV", "IV", "I", "I", "V", "IV", "I", "I"]  # 12-bar blues
"house": ["i", "VI", "III", "VII"]      # Minor house progression
"reggae": ["I", "V", "vi", "IV"]        # Offbeat rhythm style
"country": ["I", "IV", "V", "I"]        # Simple and direct
"rock": ["I", "bVII", "IV", "I"]        # Power chord style
"r&b": ["I", "V", "vi", "iii", "IV", "I", "IV", "V"]  # Complex R&B

# Genre-Specific Characteristics
STYLES = {
    "house": {
        "bpm": 120-128,
        "time_signature": "4/4",
        "drum_pattern": "4-on-floor kick, offbeat hats",
        "bass": "Synth bass with groove",
        "common_instruments": [38, 80, 88, 4]  # Synth bass, lead, pad, e-piano
    },
    "jazz": {
        "bpm": 100-180,
        "time_signature": "4/4 or 3/4",
        "chords": "Extended (7th, 9th, 11th, 13th)",
        "common_instruments": [0, 32, 64, 56, 73]  # Piano, bass, sax, trumpet, drums
    },
    "orchestral": {
        "bpm": 60-140,
        "sections": ["strings", "woodwinds", "brass", "percussion"],
        "common_instruments": [40, 41, 42, 56, 73, 47]  # Violin, viola, cello, trumpet, flute, timpani
    },
    "rock": {
        "bpm": 100-140,
        "time_signature": "4/4",
        "guitars": "Distorted (30) or clean (27)",
        "common_instruments": [30, 33, 0, 128]  # Distortion guitar, bass, piano, drums
    },
    "ambient": {
        "bpm": 60-90,
        "characteristics": "Long sustained notes, atmospheric pads",
        "common_instruments": [88, 89, 90, 91, 52]  # Various pads, choir
    },
    "trap": {
        "bpm": 130-170,
        "drums": "Tight snare rolls, 808 bass kicks",
        "hi_hats": "Fast hi-hat patterns (1/16 or 1/32 notes)",
        "common_instruments": [38, 128]  # Synth bass, drums
    }
}
music21 乐器类
from music21 import instrument

# Strings
instrument.Violin()
instrument.Viola()
instrument.Violoncello()  # Note: NOT Cello()
instrument.Contrabass()
instrument.Harp()

# Piano
instrument.Piano()
instrument.Harpsichord()

# Brass
instrument.Trumpet()
instrument.Trombone()
instrument.Tuba()
instrument.Horn()  # French horn

# Woodwinds
instrument.Flute()
instrument.Clarinet()
instrument.Oboe()
instrument.Bassoon()
instrument.SopranoSaxophone()
instrument.AltoSaxophone()
instrument.TenorSaxophone()  # Most common for jazz
instrument.BaritoneSaxophone()

# Other
instrument.AcousticGuitar()
instrument.ElectricGuitar()
instrument.Bass()
instrument.Timpani()

# CRITICAL: music21 has LIMITED support for electronic instruments and drums
# For synths, drums, and electronic sounds, the following steps must be taken:
# 1. Create a Part without an instrument (or use a placeholder like Piano())
# 2. Use mido library to INSERT program_change messages after export
# 3. Set drums to MIDI channel 10 (channel 9 in 0-indexed) or they won't sound like drums
#
# Common mistakes:
# - instrument.Cello() doesn't exist - use Violoncello()
# - instrument.FrenchHorn() doesn't exist - use Horn()
# - Setting part.partName doesn't change the sound - MIDI program must be set with mido
# - Drums on channel 0 will play as pitched notes, not drum sounds
音符时值(四分音符 = 1.0)
  • 全音符:4.0
  • 二分音符:2.0
  • 四分音符:1.0
  • 八分音符:0.5
  • 十六分音符:0.25
  • 附点四分音符:1.5
  • 四分三连音:0.667
mido 快速参考

对于电子音乐和鼓,在 music21 导出后使用 mido 设置 MIDI 音色:

from mido import MidiFile, Message

mid = MidiFile(midi_path)

# Define helper function
def set_track_instrument(track, program):
    """Insert a program_change message at the beginning of a MIDI track."""
    insert_pos = 0
    for j, msg in enumerate(track):
        if not msg.is_meta:
            insert_pos = j
            break
    else:
        insert_pos = len(track)
    track.insert(insert_pos, Message('program_change', program=program, time=0))

# Insert program_change messages
for i, track in enumerate(mid.tracks):
    if i == 1:  # Your track (tracks start at 1, not 0)
        set_track_instrument(track, 38)  # Synth Bass 1

# For drums: Set channel to 9 (channel 10 in 1-indexed)
for i, track in enumerate(mid.tracks):
    if i == 1:  # Drum track
        for msg in track:
            if hasattr(msg, 'channel'):
                msg.channel = 9

mid.save(midi_path)

常见 MIDI 音色:

  • 38:合成贝斯 1
  • 80:方波主音
  • 81:锯齿波主音
  • 88:铺底音色 1(新世纪)
  • 25:钢弦原声吉他 — 音量大,穿透力强
  • 33:原声贝斯

常见技巧

鼓编程(四拍底鼓 House 节拍)

关键:music21 的 .append() 会按顺序添加音符(一个接一个),而不是同时添加。对于底鼓、军鼓和踩镲同时演奏的分层鼓点,必须使用 .insert(offset, note) 并明确指定时间。

添加音符到声部时始终使用 .insert():

由于几乎所有优秀的音乐创作都需要分层,所有音轨(鼓、贝斯、吉他、铺底音色等)都必须始终使用 part.insert(offset, note)。这样可以避免时序错误,并确保正确同步。

添加音符到声部时,切勿混用 .insert() 和 .append()——如果鼓使用了 .insert(),而其他乐器使用 .append(),music21 会错误计算音轨长度,导致音轨比预期长 5 到 10 倍(8 分钟而不是 1.5 分钟),且只有前 20% 到 25% 包含实际声音。

注意:使用 score.append(part) 将声部添加到乐谱中没有问题——问题具体在于向声部添加音符/和弦,而不是向乐谱添加声部。

正确写法:

# RIGHT: Use .insert() for notes, .append() for parts
drum_part = stream.Part()
bass_part = stream.Part()

offset = 0.0
for beat in range(16):
    # Kick and hi-hat on same beat (layered)
    drum_part.insert(offset, note.Note(36, quarterLength=0.5))  # Kick
    drum_part.insert(offset, note.Note(42, quarterLength=0.5))  # Hi-hat

    # Bass note
    bass_part.insert(offset, note.Note('A2', quarterLength=0.5))

    offset += 0.5  # Advance time

# Add parts to score (this .append() is fine)
score.append(drum_part)
score.append(bass_part)
确保完整覆盖每个小节

关键:使用循环作曲时,确保音符完整填满每个小节,不留空隙。常见错误是放置的音符未覆盖整个小节时长,从而留下静音间隙。

问题写法(会产生间隙):

bar_length = 1.5  # 6/8 time signature
offset = 0.0

for bar in range(8):
    # Only fills first 1.0 quarter lengths, leaving 0.5 silent!
    guitar_part.insert(offset, note.Note('D3', quarterLength=0.5))
    guitar_part.insert(offset + 0.5, chord.Chord(['D3', 'F#3', 'A3'], quarterLength=0.5))
    # Missing: offset + 1.0 to offset + 1.5

    offset += bar_length  # Advances to next bar, but gap remains

# Result: Guitar plays for 10 seconds, then silence for remaining duration

解决方案写法(完整覆盖):

bar_length = 1.5  # 6/8 time signature
offset = 0.0

for bar in range(8):
    # Fill the ENTIRE bar with notes
    guitar_part.insert(offset, note.Note('D3', quarterLength=0.5))           # 0.0 to 0.5
    guitar_part.insert(offset + 0.5, chord.Chord(['D3', 'F#3', 'A3'], quarterLength=0.5))  # 0.5 to 1.0
    guitar_part.insert(offset + 1.0, note.Note('D3', quarterLength=0.5))     # 1.0 to 1.5 ✓ Complete!

    offset += bar_length

# Result: Guitar plays continuously throughout the entire piece

最佳实践:在执行 offset += bar_length 之前,确认最后一个音符恰好在 offset + bar_length 处结束。例如:

  • 如果 bar_length = 1.5,最后一个音符应从 offset + 1.0 开始,且 quarterLength=0.5,以便延续到 offset + 1.5
  • 如果 bar_length = 2.0,最后一个音符应从 offset + 1.5 开始,且 quarterLength=0.5,以便延续到 offset + 2.0

最佳实践

作曲质量
  • 生成变化:不要在整首曲子中重复相同的 4 个小节
  • 运用乐理:使用真实的和弦进行和恰当的声部进行
  • 遵循乐器音域:小提琴(G3-E7)、大提琴(C2-C6)、小号(E3-C6)
  • 加入力度变化:使用 p、mp、mf、f、ff 力度记号和渐强
  • 安排结构:引子 → 展开 → 高潮 → 解决
  • 加入人性化处理:改变时值和力度,避免声音听起来机械
    import random
    n = note.Note('C5', quarterLength=1.0 + random.uniform(-0.05, 0.05))
    n.volume.velocity = 80 + random.randint(-5, 5)
    
技术质量
  • SoundFont:使用 FluidR3_GM.sf2 以获得最佳质量
  • 比特率:最低 192kbps,高质量使用 320kbps
  • 限幅:通过 audio.compress_dynamic_range() 应用,以防止削波
  • 时序精度:谨慎使用 quarterLength 值
  • 清理:MP3 转换后删除临时 MIDI/WAV 文件
常见问题
  • 严重:切勿假设音轨编号——不要盲目使用 if i == 3 来指定吉他。务必先检查 MIDI 文件中的音符数量,以确认各音轨的乐器
  • 严重:向声部添加音符时务必使用 part.insert(offset, note)——不要使用 part.append(note),也不要混用 .insert() 和 .append()。这会导致时序错误,使音轨时长变成预期的 5–10 倍,而只有 20–25% 的部分包含声音。详情请参阅“鼓组编程”部分
  • 严重:插入 program_change 消息——使用 track.insert(pos, Message('program_change', ...)),不要使用 msg.program = X。请参阅 mido 快速参考
  • 严重:设置力度——主旋律 90–105,背景 50–65。请参阅“混音与平衡”部分
  • 严重:低音音区——使用 A1–A2(55–110 Hz),切勿使用 A0–G0(大多数系统无法听见)
  • instrument.Cello() 不存在——请使用 Violoncello()
  • 忘记设置速度——向第一个声部添加 tempo.MetronomeMark()
  • 鼓声听起来不像鼓声——使用 mido 将通道设为 9(请参阅 mido 快速参考)

混音与平衡

严重:仅设置 MIDI 乐器编号并不足够。如果不明确控制力度,某些乐器将会完全听不见。

设置力度

music21 默认将所有音符的力度设为 64,导致混音效果不佳。导出 MIDI 后,使用 mido 设置力度:

from mido import MidiFile

def set_track_velocity(track, velocity):
    """Set velocity for all note_on messages in a track."""
    for msg in track:
        if msg.type == 'note_on' and msg.velocity > 0:
            msg.velocity = velocity

mid = MidiFile(midi_path)
for i, track in enumerate(mid.tracks):
    if i == 1:  # Drums
        set_track_velocity(track, 75)
    elif i == 2:  # Bass
        set_track_velocity(track, 80)
    elif i == 3:  # Lead instrument (sax, guitar, trumpet)
        set_track_velocity(track, 95)
    elif i == 4:  # Background (organ, pads)
        set_track_velocity(track, 55)
mid.save(midi_path)
力度指南

按角色划分:

  • 主奏乐器(旋律、独奏):90–105
  • 节奏乐器(吉他切分、伴奏):85–100
  • 低音:75–85
  • 鼓:70–90
  • 背景(铺底音色、风琴):50–65

按频率范围划分:

  • 低频(20–250 Hz):低音、底鼓——只保留一个主导声部,力度为 75–85
  • 中频(250–2000 Hz):最拥挤的频段——使用力度区分声部(主奏 90+,背景 50–65)
  • 高频(2000+ Hz):踩镲、钹——力度设为 70–85,清晰且不刺耳
Soundfont 音量问题

FluidR3_GM 中的乐器录制音量各不相同。即使力度设置正确,某些乐器仍可能听不见:

音量较小的音色(避免用于主奏或节奏声部):

  • Program 27(电吉他 - 清音)——音量很小
  • Program 24(原声吉他 - 尼龙弦)
  • Program 73(长笛)

更好的替代选项:

  • Program 25(原声吉他 - 钢弦)——音量大 8–10dB,声音更突出
  • Program 28(电吉他 - 闷音)——打击感强
  • Program 30(失真吉他)——音色强劲

其他修正方法:

  • 延长音符时值(至少 0.4 quarterLength,而非 0.25)
  • 使用八度区分(将相互冲突的乐器移至不同八度)
  • 采用极大的力度对比(较安静的乐器设为 110,较响的设为 40)
混音检查清单

渲染前:

  • ✅ 主奏力度为 90–105
  • ✅ 背景力度为 50–65
  • ✅ 低音力度为 75–85
  • ✅ 检查音量较小的乐器(programs 24、27、73),并使用替代选项
  • ✅ 节奏乐器的时值至少为 0.4 quarterLength

资源

限制

  • 仅限器乐——不含歌词或人声
  • 基于 MIDI 合成——并非录音室品质的录音
  • 不支持实时播放——必须先渲染文件才能播放

评论

支持 Markdown