「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」。
music21是由MIT开发计算音乐学分析Python库,可以处理包括MusicXML,MIDI,abc在内的等多种格式的音乐文件,网络上并没有很多中文教程,希望分享给对计算机音乐抱有同样兴趣的伙伴。
建议配合官方图片食用
音符(Notes)
标准音符的概念被包含在note的Note对象之中
直接输入note便可得到该模块的位置
如果你想知道note除了Note还包含什么,可以输入dir(note)
创建音符
让我们使用note.Note创建一个音符
f = note.Note('F5')
音符的属性
F5是这个音符的音名
通过.name .step和.octave可以得到其音名、音级、八度(在第几个八度)信息
f.name f.step f.octave
'F' 'F' 5
.step得到不包含变化音及八度信息的音名,这里成为音级严格来说并不准确
当然也可以使用.pitch直接得到其音名
f.pitch
<music21.pitch.Pitch F5>
使用.pitch.frequency得到其频率
f.pitch.frequency
音高698.456462866008
使用.pitch.pitchClass同样可以得到其音级(距离同一个八度中c的半音数字),使用 .pitch.pitchClassString则可以得到一个String
f.pitch.pitchClassf.pitch.pitchClassString type(f.pitch.pitchClassString)
音高5 '5' <class'str'>
在music21中,升降分别使用# 和-,我们创建一个新的音符
b_flat = note.Note("B2-")
使用.pitch.accidental可获得其属性(accidental在音乐中表示变音记号),使用.pitch.accident.alter获得其半音变化数量,使用.pitch.accidental.name获得其
b_flat.pitch.accidental b_flat.pitch.accidental.alter b_flat.pitch.accidental.name
<accidental flat> -1.0 'flat'
注意,这里是一个浮点数,这意味着music21支持四分音符之类现实中通常不使用的东西
此外,并不是每一个音符都有accidental的,某些音符会返回None
我们可以使用一个判断语句来解决:
if d.pitch.accidental is not None:
print(d.pitch.accidental.name)
如果你安装了MusicXML阅读器,使用f.show()可查看其五线谱
修改音符
使用.transpose修改你的音符
d = b_flat.transpose("M3") #将Bb上调大三度,变为D
这种用法并没有改变音符本身,而是返回一个变量
可以使用inplace=True进行原地操作
休止符
使用note.rest
a = note.rest() # 记得加括号
最后提醒一个点,不要使用note作为音符的变量
note = note.Note("C4")
音高(Pitch),时值(Duration)
音高
使用pitch.pitch()创建一个音高对象
p1 = pitch.Pitch('b-4')
有许多属性和note是一样的
pl.octave pl.name pl.pitchClass pl.accidental.alter
4 'B-' 10 -1.0
.transpose()同样可以使用
.nameWithOctave和.midi
pl.nameWithOctave pl.midi
'B-4' 70
这些属性大多数都可以修改
pl.name = "d#"
pl.octave = 3
pl.nameWithOctave
'D#3'
这时pl代表的音符已经变为了D#3
实际上,每一个Note对象内部,都有一个Pitch对象,我们对note.Note做的一切,都可以用note.Note.pitch对象代替
一些note不支持的属性
csharp.pitch.spanish # 获得其西班牙名称
可以使用一些其他的方法,来更清晰地打印
print(csharp.pitch.unicodeName)
C♯
获得一些同音的方法
print( csharp.pitch.getEnharmonic() )
print( csharp.pitch.getLowerEnharmonic() )
D-4
B##3
时值
任何音符的存在都离不开时值Duration
创建一个二分音符
halfDuration = duration.Duration('half')
# ‘whole','half','quarter','eighth','16th','32th','64th' 一直到'2048th',虽太小无法在乐谱上无法显示
# 'breve','longa','maxima' 2,4,8个全音
另一种创建方法是说明他有多少个四分音符
dottedQuarter = duration.Duration(1.5)
可以使用.quarterLength得到时值是多少个四分音符
还可以使用Note创建
c = note.Note("C4", type='whole')
.type可以得到一般类型,如'half','quarter'
.dots可以得到音符有多少个附点
使用.lyric添加歌词(具体看文档吧,不具体介绍了)
otherNote = note.Note("F6")
otherNote.lyric = "I'm the Queen of the Night!"
流(Stream)
我们可以通过列表对note等对象进行处理,但是它们对音乐一无所知,因此需要一个类似于列表的对象具有一定“智能”的对象,成为Stream
流有许多子类Score乐谱、Part声部、Measure小节
创建流
Stream中储存的元素必须是music21对象,如果想加入不属于music21的对象,请将其放入ElementWrapper
使用Stream()创建流,.append()方法添加元素,.repeatAppend()方法添加多个相同的音符
stream1 = stream.Stream()
stream1.append(note1)
stream1.append(note2)
stream1.append(note3)
stream2 = stream.Stream()
n3 = note.Note('D#5') # octave values can be included in creation arguments
stream2.repeatAppend(n3, 4)
使用.show('text')查看其中的内容及其偏移量(从0.0开始,一般1个偏移量指一个四分音符的长度)
stream1.show('text')
{0.0} <music21.note.Note C>
{2.0} <music21.note.Note F#>
{3.0} <music21.note.Note B->
流的大部分方法和列表相同,如切片、索引(还可使用.index()访问)、pop()、.append()、len()等,并且流中也可以存放列表
按类分离元素
提供一种过滤流以获取所需元素.getElementByClass()
for thisNote in stream1.getElementsByClass(["Note", "Rest"]):
print(thisNote, thisNote.offset)
<music21.note.Note C> 0.0
<music21.note.Note F#> 2.0
<music21.note.Note B-> 3.0
此外也可使用.notes、.notesAndRests、.pitches等来进行过滤,直接使用会返回所有,如
stream1.pitches
[<music21.pitch.Pitch D#5>,
<music21.pitch.Pitch D#5>,
<music21.pitch.Pitch D#5>,
<music21.pitch.Pitch D#5>]
for thisNote in stream1.notesAndRests:
print(thisNote)
<music21.note.Note C>
<music21.note.Note F#>
<music21.note.Note B->
通过偏移量分离元素
getElementsByOffset()
sOut = stream1.getElementsByOffset(2, 3).stream()
sOut.show('text')
{2.0} <music21.note.Note F#>
{3.0} <music21.note.Note B->
还有getElementAtOrBefore()(某个偏移量及其之前score = stream.Score() 1),getElementAfterElement()(某个偏移量之后)
更多功能
.analyze('ambitus')获得流中的音域范围
.lowestOffset返回偏移量的最小值
__repr__
.id,可以自己设定,相当于名字,如
s = stream.Score(id='mainScore')
p0 = stream.Part(id='part0')
p1 = stream.Part(id='part1')
.duration储存Duration对象的属性
小节(Measure)
可以使用corpus访问大量的乐谱,使用Parse()从语料库中解析出Score(一种流的子类)
sBach = corpus.parse('bach/bwv57.8')
它包含一个Metadata对象、一个 StaffGroup对象和四个 Part对象。
可以使用measures()或measure()获取多个或一个小节,前者获取整个乐曲所有Part的小节,后者必须对一个Measure对象
然而一个问题是,这与使用getElementsByClass(stream.Measure)并不相同,因为在乐曲中存在小节并不连续的情况
递归方法
.recurse()可以访问流中的每一个元素,若任何子元素也是流,他将访问该流中的每一个元素
他会返回一个生成器,使用循环来访问每一个元素
recurseScore = s.recurse()
recurseScore
<music21.stream.iterator.RecursiveIterator for Score:mainScore @:0>
for el in s.recurse():
print(el.offset, el, el.activeSite)
0.0 <music21.stream.Part part0> <music21.stream.Score mainScore>
0.0 <music21.stream.Measure 1 offset=0.0> <music21.stream.Part part0>
0.0 <music21.note.Note C> <music21.stream.Measure 1 offset=0.0>
4.0 <music21.stream.Measure 2 offset=4.0> <music21.stream.Part part0>
0.0 <music21.note.Note D> <music21.stream.Measure 2 offset=4.0>
0.0 <music21.stream.Part part1> <music21.stream.Score mainScore>
0.0 <music21.stream.Measure 1 offset=0.0> <music21.stream.Part part1>
0.0 <music21.note.Note E> <music21.stream.Measure 1 offset=0.0>
4.0 <music21.stream.Measure 2 offset=4.0> <music21.stream.Part part1>
0.0 <music21.note.Note F> <music21.stream.Measure 2 offset=4.0>
大多数过滤方法也可以用于该生成器
扁平化流
.flat可以将嵌套流打平,并赋予新的offset
和弦(Chord)
和弦(Chord)
创建和弦
cMinor = chord.Chord(["C4","G4","E5-"]) # 通过音名的列表创建
d = note.Note('D4')
fSharp = note.Note('F#4')
a = note.Note('A5')
dMajor = chord.Chord([d, fSharp, a]) # 添加已有的音符
e7 = chord.Chord("E4 G#4 B4 D5") # 通过带有空格的字符串创建
es = chord.Chord("E- G B-") # 如果所有音符的都在一个八度内,则不需要八度信息
功能
Chord和Note对象都是GneralNote对象的子类,因此大部分Note的属性,Chord都能使用
其中,音高由.pitch被替换为.pitches
一些其他功能
cMinor.isMinorTriad() # 是否是小三和弦
cMinor.isMajorTriad() # 是否是大三和弦
cMinor.inversion() # 是否处于转位状态
cMinor.add() # 添加音符
cMinor.remove() # 移除音符
cClosed = cMinor.closedPosition() # 封闭和弦,不会该改变原对象
semiClosedPosition() # 不太懂
cn1 = cMinor.commonName # 得到和弦的名字
fMajor.fullName # 包含step和时值
fMajor.pitchedCommonName # 加入了pitch信息
和弦和音符一样可以添加到流之中
加载文件
使用corpus.parse()从语料库加载文件
使用converter.parse()从本地磁盘或网络加载文件
如果文件名没有后缀或者后缀有误,可以使用format="FORMAT"
music21会在第一遍读取文件后保存优化版本,若下次读取时检测到没有更改文件,那么速度将会提升2到5倍(大概是通过检查文件修改时间是否变动,可以使用forceSource=True来保证重新加载原文件)
使用converter.Converter().subconvertersList('input')得到其可以读入的所有格式
使用converter.Converter().subconvertersList('output')得到其可以写的所有格式
其可以使用的文件格式有Humdrum、MusicXML、MusicXML、Musedata、MIDI等