
本文介绍如何利用 music21 的 scale 类精准生成 c 大调(c ionian)自然音阶中的音符,确保所有输出音符均在指定 midi 音高范围内且严格不含升降号。
本文介绍如何利用 music21 的 scale 类精准生成 c 大调(c ionian)自然音阶中的音符,确保所有输出音符均在指定 midi 音高范围内且严格不含升降号。
原始代码的问题在于:它先随机选择音名(如 'C', 'D'…),再强行将 .pitch.midi 设为 60–80 区间内的任意整数——这会覆盖原有音名对应的音高逻辑,导致出现 C#4、Fb5 等非自然音(即非 C 大调音阶成员)。正确做法是先定义 C 大调音阶,再在其合法音高范围内采样。
music21 提供了完善的调式与音阶支持。推荐使用 scale.ConcreteScale 显式构造 C 大调音阶,并调用 getPitches(low, high) 获取该音阶在指定音域(支持跨八度)内所有合法音高(返回 list[ Pitch ]),再从中随机选取:
from music21 import stream, note, scale, pitch
def generate_c_major_diatonic_notes(num_notes, min_midi=60, max_midi=80):
# 构建 C 大调自然音阶(C Ionian)
c_major_scale = scale.ConcreteScale(pitches=['C', 'D', 'E', 'F', 'G', 'A', 'B'])
# 获取该音阶在 [min_midi, max_midi] 范围内的所有合法音高(自动跨八度)
# 注意:getPitches 接收 Pitch 对象作为边界,需先转换
low_p = pitch.Pitch()
low_p.midi = min_midi
high_p = pitch.Pitch()
high_p.midi = max_midi
all_diatonic_pitches = c_major_scale.getPitches(low_p, high_p)
# 随机采样(允许重复)
s = stream.Stream()
for _ in range(num_notes):
p = random.choice(all_diatonic_pitches)
n = note.Note(p)
n.duration = 'eighth'
s.append(n)
return s
# 示例:生成 20 个 C 大调自然音阶音符,MIDI 音高范围 60–80(即 C4–C5+)
diatonic_stream = generate_c_major_diatonic_notes(num_notes=20, min_midi=60, max_midi=80)
diatonic_stream.show('midi') # 导出或播放验证✅ 关键优势:
- getPitches() 自动处理跨八度扩展,例如 C4, C5, C6 均会被包含(只要在 min_midi–max_midi 内);
- 所有返回的 Pitch 对象天然属于 C 大调音阶,n.name 恒为 'C', 'D', 'E', 'F', 'G', 'A', 'B'(无升降号);
- 避免手动映射音名与 MIDI 值,杜绝非法音高(如 E#4 或 Cb5)。
⚠️ 注意事项:
- min_midi 和 max_midi 必须对应真实存在的音高(如 60 是 C4,80 是 C6),否则 getPitches() 可能返回空列表;
- 若需确保每个音符音高唯一(不重复),可改用 random.sample(all_diatonic_pitches, k=min(num_notes, len(all_diatonic_pitches)));
- 如需其他调式(如 G 大调),仅需修改 pitches=['G','A','B','C','D','E','F#'] 并注意升降号书写规范('F#' 而非 'F#' 或 'Fsharp')。
此方法兼顾准确性、可扩展性与 music21 最佳实践,是生成调性明确、音高合规旋律片段的可靠方案。

















