关于 code-to-music
code-to-music 是一款按请求编写程序化音乐的 skill,提供电子与传统音乐渲染流程,并指导生成 MP3 文件。
主要功能
- 01算法作曲指导
- 02电子与传统音乐流程
- 03使用 mido 分配 MIDI 乐器
- 04MIDI 渲染与音频校验
适用场景
- 01根据需求创作电子音乐
- 02制作原声或管弦乐编曲
- 03将 MIDI 乐曲渲染为 MP3
- 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.mdinstall.shrequirements.txttraditional-music-pipeline.mdscripts/__init__.pyaudio_validate.pymidi_render.pymidi_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 音乐结构渲染为 MP3midi_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
资源
- music21 文档:https://web.mit.edu/music21/doc/
- 通用 MIDI 规范:https://www.midi.org/specifications-old/item/gm-level-1-sound-set
- 乐理:https://www.musictheory.net/
- IMSLP(免费乐谱):https://imslp.org/ - 在此下载古典音乐 MIDI!
限制
- 仅限器乐——不含歌词或人声
- 基于 MIDI 合成——并非录音室品质的录音
- 不支持实时播放——必须先渲染文件才能播放